From c33d7aa5a005da1e7a6452a06f6ae7c09fdeb282 Mon Sep 17 00:00:00 2001 From: JStone2934 <91839415+JStone2934@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:35:28 +0800 Subject: [PATCH 001/147] Update full_description.txt --- fastlane/metadata/android/zh-CN/full_description.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt index 34face7..b08a027 100644 --- a/fastlane/metadata/android/zh-CN/full_description.txt +++ b/fastlane/metadata/android/zh-CN/full_description.txt @@ -2,4 +2,8 @@ 作为整活向APP,目前没有接入AI,可以离线运行 -目前检测到提问时会弹出对话框,后续可以加更好玩的功能. +目前检测到提问时会弹出对话框,可以点击齿轮按钮设置触发的关键词 + +加入了存档功能,按下SAVE和Q.SAVE可以截屏 + +后续可以增加更好玩的功能 From cf8de0460fcab60f4d8c2e36bc4dec24ae0316a3 Mon Sep 17 00:00:00 2001 From: JStone2934 <91839415+JStone2934@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:26:19 +0800 Subject: [PATCH 002/147] Update README with new features and download info Added information about frame rate control and download instructions. --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f83ba70..60de282 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,14 @@ 作为整活向APP,目前没有接入AI,可以离线运行 +### 画面卡顿是为了复刻GalGame切换CG的效果,后续会开发可以手动切换更新帧率的功能。 + APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载,报毒是正常状况,请放心使用 + + +## 使用教程 -目前检测到提问时会弹出对话框,后续可以加更好玩的功能. +点击齿轮按钮可以设置触发弹窗的关键词,SAVE和Q.SAVE可以保存当前截屏到手机。好感度条会随着时间慢慢减少,有语音的时候会不断提高,选择不同的弹窗选项也会影响好感度。 image @@ -52,4 +57,4 @@ CSDN下载链接:https://download.csdn.net/download/qq_63533710/92237453 并把 `release.keystore` 放在合适位置(与 `signing.properties` 中 path 对应)。请妥善保管密钥与密码。 -## 欢迎自由开发,有活你就直接往里加 \ No newline at end of file +## 欢迎自由开发,有活你就直接往里加 From 404bf1a76372497cf05eb7c9e86272dfc11f8e14 Mon Sep 17 00:00:00 2001 From: JStone2934 <91839415+JStone2934@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:33:21 +0800 Subject: [PATCH 003/147] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e2caee5..8260e13 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,7 @@ CSDN下载链接:https://download.csdn.net/download/qq_63533710/92237453 并把 `release.keystore` 放在合适位置(与 `signing.properties` 中 path 对应)。请妥善保管密钥与密码。 -## 贡献者 -![cr](https://contrib.rocks/image?repo=JStone2934/LiveGalGame) ## Star From 469bd8a6656c2c08eb076b92151a58746bcc80b0 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 10:54:59 +0800 Subject: [PATCH 004/147] =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=BC=B9=E7=AA=97=EF=BC=8C=E4=BD=86=E6=98=AF=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=9C=89BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/livegg1/MainActivity.kt | 79 ++++++----- .../livegg1/dialog/TriggerManagementDialog.kt | 126 ++++++++++++++++-- .../example/livegg1/dialog/keywordDialog.kt | 20 +-- .../java/com/example/livegg1/model/Trigger.kt | 4 +- 4 files changed, 175 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/example/livegg1/MainActivity.kt b/app/src/main/java/com/example/livegg1/MainActivity.kt index a2a3632..8c936ab 100644 --- a/app/src/main/java/com/example/livegg1/MainActivity.kt +++ b/app/src/main/java/com/example/livegg1/MainActivity.kt @@ -158,42 +158,46 @@ class MainActivity : ComponentActivity() { ) if (showKeywordDialog) { - KeywordDialog( - onAccept = { - Log.d("MainActivity", "Keyword accepted: ${activeTrigger?.keyword}") - idleBgmAsset = "Ah.mp3" - queueAffectionChange(-0.4f) - showKeywordDialog = false - activeTrigger = null - if (!showTriggerDialog) { - restartListeningIfPossible() - } - }, - onReject = { - Log.d("MainActivity", "Keyword rejected: ${activeTrigger?.keyword}") - idleBgmAsset = "casual.mp3" - queueAffectionChange(0.4f) - showKeywordDialog = false - activeTrigger = null - if (!showTriggerDialog) { - restartListeningIfPossible() - } - }, - onDismiss = { - showKeywordDialog = false - activeTrigger = null - if (!showTriggerDialog) { - restartListeningIfPossible() - } - }, - onSelectBgm = { asset -> idleBgmAsset = asset } - ) + activeTrigger?.let { trigger -> + KeywordDialog( + primaryOptionLabel = trigger.primaryOptionText, + secondaryOptionLabel = trigger.secondaryOptionText, + onPrimarySelected = { + Log.d("MainActivity", "Keyword rejected: ${trigger.keyword}") + idleBgmAsset = "casual.mp3" + queueAffectionChange(0.4f) + showKeywordDialog = false + activeTrigger = null + if (!showTriggerDialog) { + restartListeningIfPossible() + } + }, + onSecondarySelected = { + Log.d("MainActivity", "Keyword accepted: ${trigger.keyword}") + idleBgmAsset = "Ah.mp3" + queueAffectionChange(-0.4f) + showKeywordDialog = false + activeTrigger = null + if (!showTriggerDialog) { + restartListeningIfPossible() + } + }, + onDismiss = { + showKeywordDialog = false + activeTrigger = null + if (!showTriggerDialog) { + restartListeningIfPossible() + } + }, + onSelectBgm = { asset -> idleBgmAsset = asset } + ) + } } if (showTriggerDialog) { TriggerManagementDialog( triggers = triggers, - onAddTrigger = { keyword, dialogType -> + onAddTrigger = { keyword, dialogType, primaryOptionText, secondaryOptionText -> val cleaned = keyword.trim() val duplicate = triggers.any { it.keyword.equals(cleaned, ignoreCase = true) } if (cleaned.isEmpty()) { @@ -201,7 +205,12 @@ class MainActivity : ComponentActivity() { } else if (duplicate) { Log.w("MainActivity", "Keyword already exists: $cleaned") } else { - val newTrigger = KeywordTrigger(keyword = cleaned, dialogType = dialogType) + val newTrigger = KeywordTrigger( + keyword = cleaned, + dialogType = dialogType, + primaryOptionText = primaryOptionText.trim().ifEmpty { primaryOptionText }, + secondaryOptionText = secondaryOptionText.trim().ifEmpty { secondaryOptionText } + ) Log.d("MainActivity", "Keyword added: ${newTrigger.keyword}") triggers = triggers + newTrigger } @@ -216,7 +225,11 @@ class MainActivity : ComponentActivity() { } else if (duplicate) { Log.w("MainActivity", "Keyword already exists: $cleaned") } else { - val sanitized = updated.copy(keyword = cleaned) + val sanitized = updated.copy( + keyword = cleaned, + primaryOptionText = updated.primaryOptionText.trim().ifEmpty { updated.primaryOptionText }, + secondaryOptionText = updated.secondaryOptionText.trim().ifEmpty { updated.secondaryOptionText } + ) Log.d("MainActivity", "Keyword updated: ${sanitized.keyword}") triggers = triggers.map { existing -> if (existing.id == original.id) sanitized else existing diff --git a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt index 21f4993..d0eda21 100644 --- a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt +++ b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt @@ -1,7 +1,6 @@ package com.example.livegg1.dialog import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -25,7 +24,7 @@ import com.example.livegg1.model.KeywordTrigger @Composable fun TriggerManagementDialog( triggers: List, - onAddTrigger: (keyword: String, dialogType: DialogType) -> Unit, + onAddTrigger: (keyword: String, dialogType: DialogType, primaryOptionText: String, secondaryOptionText: String) -> Unit, onUpdateTrigger: (original: KeywordTrigger, updated: KeywordTrigger) -> Unit, onDeleteTrigger: (trigger: KeywordTrigger) -> Unit, onDismiss: () -> Unit @@ -90,12 +89,20 @@ fun TriggerManagementDialog( AddOrEditTriggerDialog( trigger = showAddOrEditDialog, isEditing = isEditing, - onConfirm = { keyword, dialogType -> + onConfirm = { keyword, dialogType, primaryOptionText, secondaryOptionText -> if (isEditing) { val original = showAddOrEditDialog!! - onUpdateTrigger(original, original.copy(keyword = keyword, dialogType = dialogType)) + onUpdateTrigger( + original, + original.copy( + keyword = keyword, + dialogType = dialogType, + primaryOptionText = primaryOptionText, + secondaryOptionText = secondaryOptionText + ) + ) } else { - onAddTrigger(keyword, dialogType) + onAddTrigger(keyword, dialogType, primaryOptionText, secondaryOptionText) } showAddOrEditDialog = null }, @@ -125,6 +132,18 @@ private fun TriggerItem( Column { Text(text = "关键词: \"${trigger.keyword}\"", fontWeight = FontWeight.SemiBold) Text(text = "触发弹窗: ${trigger.dialogType.name}", style = MaterialTheme.typography.bodySmall) + if (trigger.dialogType == DialogType.CHOICE_DIALOG) { + Text( + text = "选项1: \"${trigger.primaryOptionText}\"", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = "选项2: \"${trigger.secondaryOptionText}\"", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } Row { IconButton(onClick = { onEdit(trigger) }) { @@ -143,12 +162,16 @@ private fun TriggerItem( private fun AddOrEditTriggerDialog( trigger: KeywordTrigger?, isEditing: Boolean, - onConfirm: (keyword: String, dialogType: DialogType) -> Unit, + onConfirm: (keyword: String, dialogType: DialogType, primaryOptionText: String, secondaryOptionText: String) -> Unit, onDismiss: () -> Unit ) { var keyword by remember { mutableStateOf(trigger?.keyword ?: "") } var dialogType by remember { mutableStateOf(trigger?.dialogType ?: DialogType.CHOICE_DIALOG) } + var primaryOption by remember { mutableStateOf(trigger?.primaryOptionText ?: "好啊好啊") } + var secondaryOption by remember { mutableStateOf(trigger?.secondaryOptionText ?: "不了") } var keywordError by remember { mutableStateOf(false) } + var primaryOptionError by remember { mutableStateOf(false) } + var secondaryOptionError by remember { mutableStateOf(false) } AlertDialog( onDismissRequest = onDismiss, @@ -159,7 +182,9 @@ private fun AddOrEditTriggerDialog( value = keyword, onValueChange = { keyword = it - keywordError = it.isBlank() + if (keywordError) { + keywordError = it.isBlank() + } }, label = { Text("关键词") }, isError = keywordError, @@ -167,7 +192,11 @@ private fun AddOrEditTriggerDialog( modifier = Modifier.fillMaxWidth() ) if (keywordError) { - Text("关键词不能为空", color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + Text( + "关键词不能为空", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall + ) } Spacer(Modifier.height(16.dp)) @@ -179,7 +208,13 @@ private fun AddOrEditTriggerDialog( DialogType.values().forEach { type -> val isSelected = dialogType == type Button( - onClick = { dialogType = type }, + onClick = { + dialogType = type + if (type != DialogType.CHOICE_DIALOG) { + primaryOptionError = false + secondaryOptionError = false + } + }, colors = ButtonDefaults.buttonColors( containerColor = if (isSelected) MaterialTheme.colorScheme.primary else Color.Gray ) @@ -188,15 +223,82 @@ private fun AddOrEditTriggerDialog( } } } + + if (dialogType == DialogType.CHOICE_DIALOG) { + Spacer(Modifier.height(16.dp)) + Text("自定义选项内容:", modifier = Modifier.padding(bottom = 8.dp)) + + OutlinedTextField( + value = primaryOption, + onValueChange = { + primaryOption = it + if (primaryOptionError) { + primaryOptionError = it.isBlank() + } + }, + label = { Text("选项1 文本") }, + isError = primaryOptionError, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + if (primaryOptionError) { + Text( + "选项内容不能为空", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall + ) + } + + Spacer(Modifier.height(12.dp)) + + OutlinedTextField( + value = secondaryOption, + onValueChange = { + secondaryOption = it + if (secondaryOptionError) { + secondaryOptionError = it.isBlank() + } + }, + label = { Text("选项2 文本") }, + isError = secondaryOptionError, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + if (secondaryOptionError) { + Text( + "选项内容不能为空", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall + ) + } + } } }, confirmButton = { Button( onClick = { - if (keyword.isNotBlank()) { - onConfirm(keyword, dialogType) + val sanitizedKeyword = keyword.trim() + val sanitizedPrimary = primaryOption.trim() + val sanitizedSecondary = secondaryOption.trim() + + keywordError = sanitizedKeyword.isEmpty() + val requiresOptions = dialogType == DialogType.CHOICE_DIALOG + if (requiresOptions) { + primaryOptionError = sanitizedPrimary.isEmpty() + secondaryOptionError = sanitizedSecondary.isEmpty() } else { - keywordError = true + primaryOptionError = false + secondaryOptionError = false + } + + val optionsValid = !requiresOptions || (!primaryOptionError && !secondaryOptionError) + if (!keywordError && optionsValid) { + onConfirm( + sanitizedKeyword, + dialogType, + sanitizedPrimary.ifEmpty { primaryOption }, + sanitizedSecondary.ifEmpty { secondaryOption } + ) } } ) { diff --git a/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt b/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt index b2c4267..cbcdcd0 100644 --- a/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt +++ b/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt @@ -24,8 +24,10 @@ import androidx.compose.ui.tooling.preview.Preview @Composable @OptIn(ExperimentalMaterial3Api::class) fun KeywordDialog( - onAccept: () -> Unit, - onReject: () -> Unit, + primaryOptionLabel: String, + secondaryOptionLabel: String, + onPrimarySelected: () -> Unit, + onSecondarySelected: () -> Unit, onDismiss: () -> Unit, onSelectBgm: (String) -> Unit ) { @@ -62,7 +64,7 @@ fun KeywordDialog( OutlinedButton( modifier = Modifier.fillMaxWidth(), onClick = { - onReject() + onPrimarySelected() onSelectBgm("casual.mp3") }, shape = dialogShape, @@ -72,12 +74,12 @@ fun KeywordDialog( contentColor = borderColor ) ) { - Text("好啊好啊") + Text(primaryOptionLabel) } OutlinedButton( modifier = Modifier.fillMaxWidth(), onClick = { - onAccept() + onSecondarySelected() onSelectBgm("Ah.mp3") }, shape = dialogShape, @@ -87,7 +89,7 @@ fun KeywordDialog( contentColor = borderColor ) ) { - Text("不了") + Text(secondaryOptionLabel) } } } @@ -99,8 +101,10 @@ fun KeywordDialog( @Composable private fun KeywordDialogPreview() { KeywordDialog( - onAccept = {}, - onReject = {}, + primaryOptionLabel = "好啊好啊", + secondaryOptionLabel = "不了", + onPrimarySelected = {}, + onSecondarySelected = {}, onDismiss = {}, onSelectBgm = {} ) diff --git a/app/src/main/java/com/example/livegg1/model/Trigger.kt b/app/src/main/java/com/example/livegg1/model/Trigger.kt index ebfa5b7..89c4f48 100644 --- a/app/src/main/java/com/example/livegg1/model/Trigger.kt +++ b/app/src/main/java/com/example/livegg1/model/Trigger.kt @@ -12,5 +12,7 @@ enum class DialogType { data class KeywordTrigger( val id: String = UUID.randomUUID().toString(), // 使用UUID确保唯一性 val keyword: String, - val dialogType: DialogType + val dialogType: DialogType, + val primaryOptionText: String = "好啊好啊", + val secondaryOptionText: String = "不了" ) From 8002b1a5306a3d86a641c317b694a519300149f5 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 10:58:26 +0800 Subject: [PATCH 005/147] =?UTF-8?q?=E5=85=81=E8=AE=B8=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/livegg1/dialog/TriggerManagementDialog.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt index d0eda21..2113137 100644 --- a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt +++ b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt @@ -2,6 +2,8 @@ package com.example.livegg1.dialog import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape @@ -172,12 +174,17 @@ private fun AddOrEditTriggerDialog( var keywordError by remember { mutableStateOf(false) } var primaryOptionError by remember { mutableStateOf(false) } var secondaryOptionError by remember { mutableStateOf(false) } + val scrollState = rememberScrollState() AlertDialog( onDismissRequest = onDismiss, title = { Text(if (isEditing) "编辑触发器" else "添加触发器") }, text = { - Column { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(scrollState) + ) { OutlinedTextField( value = keyword, onValueChange = { From 294c73dbeff8e4389d3fef37eabd01040b752567 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 11:33:26 +0800 Subject: [PATCH 006/147] =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=A2=9D=E5=A4=96=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/livegg1/MainActivity.kt | 46 +-- .../livegg1/dialog/TriggerManagementDialog.kt | 293 +++++++++++++----- .../example/livegg1/dialog/keywordDialog.kt | 71 ++--- .../java/com/example/livegg1/model/Trigger.kt | 19 +- 4 files changed, 281 insertions(+), 148 deletions(-) diff --git a/app/src/main/java/com/example/livegg1/MainActivity.kt b/app/src/main/java/com/example/livegg1/MainActivity.kt index 8c936ab..d03f9c8 100644 --- a/app/src/main/java/com/example/livegg1/MainActivity.kt +++ b/app/src/main/java/com/example/livegg1/MainActivity.kt @@ -160,22 +160,14 @@ class MainActivity : ComponentActivity() { if (showKeywordDialog) { activeTrigger?.let { trigger -> KeywordDialog( - primaryOptionLabel = trigger.primaryOptionText, - secondaryOptionLabel = trigger.secondaryOptionText, - onPrimarySelected = { - Log.d("MainActivity", "Keyword rejected: ${trigger.keyword}") - idleBgmAsset = "casual.mp3" - queueAffectionChange(0.4f) - showKeywordDialog = false - activeTrigger = null - if (!showTriggerDialog) { - restartListeningIfPossible() - } - }, - onSecondarySelected = { - Log.d("MainActivity", "Keyword accepted: ${trigger.keyword}") - idleBgmAsset = "Ah.mp3" - queueAffectionChange(-0.4f) + options = trigger.options, + onOptionSelected = { option -> + Log.d( + "MainActivity", + "Option selected: ${option.label} for keyword ${trigger.keyword}" + ) + idleBgmAsset = option.bgmAsset + queueAffectionChange(option.affectionDelta) showKeywordDialog = false activeTrigger = null if (!showTriggerDialog) { @@ -188,8 +180,7 @@ class MainActivity : ComponentActivity() { if (!showTriggerDialog) { restartListeningIfPossible() } - }, - onSelectBgm = { asset -> idleBgmAsset = asset } + } ) } } @@ -197,7 +188,7 @@ class MainActivity : ComponentActivity() { if (showTriggerDialog) { TriggerManagementDialog( triggers = triggers, - onAddTrigger = { keyword, dialogType, primaryOptionText, secondaryOptionText -> + onAddTrigger = { keyword, dialogType, options -> val cleaned = keyword.trim() val duplicate = triggers.any { it.keyword.equals(cleaned, ignoreCase = true) } if (cleaned.isEmpty()) { @@ -205,11 +196,16 @@ class MainActivity : ComponentActivity() { } else if (duplicate) { Log.w("MainActivity", "Keyword already exists: $cleaned") } else { + val sanitizedOptions = options.map { option -> + option.copy( + label = option.label.trim().ifEmpty { option.label }, + bgmAsset = option.bgmAsset.trim().ifEmpty { option.bgmAsset } + ) + } val newTrigger = KeywordTrigger( keyword = cleaned, dialogType = dialogType, - primaryOptionText = primaryOptionText.trim().ifEmpty { primaryOptionText }, - secondaryOptionText = secondaryOptionText.trim().ifEmpty { secondaryOptionText } + options = sanitizedOptions ) Log.d("MainActivity", "Keyword added: ${newTrigger.keyword}") triggers = triggers + newTrigger @@ -227,8 +223,12 @@ class MainActivity : ComponentActivity() { } else { val sanitized = updated.copy( keyword = cleaned, - primaryOptionText = updated.primaryOptionText.trim().ifEmpty { updated.primaryOptionText }, - secondaryOptionText = updated.secondaryOptionText.trim().ifEmpty { updated.secondaryOptionText } + options = updated.options.map { option -> + option.copy( + label = option.label.trim().ifEmpty { option.label }, + bgmAsset = option.bgmAsset.trim().ifEmpty { option.bgmAsset } + ) + } ) Log.d("MainActivity", "Keyword updated: ${sanitized.keyword}") triggers = triggers.map { existing -> diff --git a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt index 2113137..cbde454 100644 --- a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt +++ b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete @@ -17,23 +18,24 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import com.example.livegg1.model.DialogType import com.example.livegg1.model.KeywordTrigger +import com.example.livegg1.model.TriggerDefaults +import com.example.livegg1.model.TriggerOption @OptIn(ExperimentalMaterial3Api::class) @Composable fun TriggerManagementDialog( triggers: List, - onAddTrigger: (keyword: String, dialogType: DialogType, primaryOptionText: String, secondaryOptionText: String) -> Unit, + onAddTrigger: (keyword: String, dialogType: DialogType, options: List) -> Unit, onUpdateTrigger: (original: KeywordTrigger, updated: KeywordTrigger) -> Unit, onDeleteTrigger: (trigger: KeywordTrigger) -> Unit, onDismiss: () -> Unit ) { var showAddOrEditDialog by remember { mutableStateOf(null) } - // A dummy trigger to represent the "add" state - val addStateTrigger = KeywordTrigger(keyword = "", dialogType = DialogType.CHOICE_DIALOG) Dialog(onDismissRequest = onDismiss) { Card( @@ -71,7 +73,9 @@ fun TriggerManagementDialog( horizontalArrangement = Arrangement.SpaceBetween ) { Button( - onClick = { showAddOrEditDialog = addStateTrigger }, + onClick = { + showAddOrEditDialog = KeywordTrigger(keyword = "", dialogType = DialogType.CHOICE_DIALOG) + }, colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary) ) { Icon(Icons.Default.Add, contentDescription = "添加") @@ -91,7 +95,7 @@ fun TriggerManagementDialog( AddOrEditTriggerDialog( trigger = showAddOrEditDialog, isEditing = isEditing, - onConfirm = { keyword, dialogType, primaryOptionText, secondaryOptionText -> + onConfirm = { keyword, dialogType, options -> if (isEditing) { val original = showAddOrEditDialog!! onUpdateTrigger( @@ -99,12 +103,11 @@ fun TriggerManagementDialog( original.copy( keyword = keyword, dialogType = dialogType, - primaryOptionText = primaryOptionText, - secondaryOptionText = secondaryOptionText + options = options ) ) } else { - onAddTrigger(keyword, dialogType, primaryOptionText, secondaryOptionText) + onAddTrigger(keyword, dialogType, options) } showAddOrEditDialog = null }, @@ -135,16 +138,13 @@ private fun TriggerItem( Text(text = "关键词: \"${trigger.keyword}\"", fontWeight = FontWeight.SemiBold) Text(text = "触发弹窗: ${trigger.dialogType.name}", style = MaterialTheme.typography.bodySmall) if (trigger.dialogType == DialogType.CHOICE_DIALOG) { - Text( - text = "选项1: \"${trigger.primaryOptionText}\"", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text( - text = "选项2: \"${trigger.secondaryOptionText}\"", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + trigger.options.forEachIndexed { index, option -> + Text( + text = "选项${index + 1}: \"${option.label}\" | BGM: ${option.bgmAsset} | Δ好感: ${option.affectionDelta}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } } Row { @@ -164,16 +164,28 @@ private fun TriggerItem( private fun AddOrEditTriggerDialog( trigger: KeywordTrigger?, isEditing: Boolean, - onConfirm: (keyword: String, dialogType: DialogType, primaryOptionText: String, secondaryOptionText: String) -> Unit, + onConfirm: (keyword: String, dialogType: DialogType, options: List) -> Unit, onDismiss: () -> Unit ) { - var keyword by remember { mutableStateOf(trigger?.keyword ?: "") } - var dialogType by remember { mutableStateOf(trigger?.dialogType ?: DialogType.CHOICE_DIALOG) } - var primaryOption by remember { mutableStateOf(trigger?.primaryOptionText ?: "好啊好啊") } - var secondaryOption by remember { mutableStateOf(trigger?.secondaryOptionText ?: "不了") } - var keywordError by remember { mutableStateOf(false) } - var primaryOptionError by remember { mutableStateOf(false) } - var secondaryOptionError by remember { mutableStateOf(false) } + var keyword by remember(trigger?.id) { mutableStateOf(trigger?.keyword ?: "") } + var dialogType by remember(trigger?.id) { mutableStateOf(trigger?.dialogType ?: DialogType.CHOICE_DIALOG) } + val optionStates = remember(trigger?.id) { + mutableStateListOf().apply { + val seed = trigger?.options ?: TriggerDefaults.defaultOptions(DialogType.CHOICE_DIALOG) + seed.forEach { option -> + add( + OptionFormState( + id = option.id, + label = option.label, + bgmAsset = option.bgmAsset, + affectionDelta = option.affectionDelta.toString() + ) + ) + } + } + } + var keywordError by remember(trigger?.id) { mutableStateOf(false) } + var optionCountError by remember(trigger?.id) { mutableStateOf(false) } val scrollState = rememberScrollState() AlertDialog( @@ -217,10 +229,7 @@ private fun AddOrEditTriggerDialog( Button( onClick = { dialogType = type - if (type != DialogType.CHOICE_DIALOG) { - primaryOptionError = false - secondaryOptionError = false - } + optionCountError = false }, colors = ButtonDefaults.buttonColors( containerColor = if (isSelected) MaterialTheme.colorScheme.primary else Color.Gray @@ -235,49 +244,45 @@ private fun AddOrEditTriggerDialog( Spacer(Modifier.height(16.dp)) Text("自定义选项内容:", modifier = Modifier.padding(bottom = 8.dp)) - OutlinedTextField( - value = primaryOption, - onValueChange = { - primaryOption = it - if (primaryOptionError) { - primaryOptionError = it.isBlank() + optionStates.forEachIndexed { index, option -> + OptionEditor( + index = index, + totalCount = optionStates.size, + state = option, + onStateChange = { updated -> optionStates[index] = updated }, + onRemove = { + if (optionStates.size > 2) { + optionStates.removeAt(index) + optionCountError = optionStates.size < 2 + } } - }, - label = { Text("选项1 文本") }, - isError = primaryOptionError, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - if (primaryOptionError) { - Text( - "选项内容不能为空", - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodySmall ) + Spacer(Modifier.height(12.dp)) } - Spacer(Modifier.height(12.dp)) - - OutlinedTextField( - value = secondaryOption, - onValueChange = { - secondaryOption = it - if (secondaryOptionError) { - secondaryOptionError = it.isBlank() - } - }, - label = { Text("选项2 文本") }, - isError = secondaryOptionError, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - if (secondaryOptionError) { + if (optionCountError) { Text( - "选项内容不能为空", + "至少需要两个选项", color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall ) } + + OutlinedButton( + onClick = { + optionStates.add( + OptionFormState( + label = "选项${optionStates.size + 1}", + bgmAsset = "", + affectionDelta = "0.0" + ) + ) + optionCountError = false + } + ) { + Icon(Icons.Default.Add, contentDescription = "添加选项") + Text("添加选项", modifier = Modifier.padding(start = 4.dp)) + } } } }, @@ -285,27 +290,57 @@ private fun AddOrEditTriggerDialog( Button( onClick = { val sanitizedKeyword = keyword.trim() - val sanitizedPrimary = primaryOption.trim() - val sanitizedSecondary = secondaryOption.trim() - keywordError = sanitizedKeyword.isEmpty() val requiresOptions = dialogType == DialogType.CHOICE_DIALOG - if (requiresOptions) { - primaryOptionError = sanitizedPrimary.isEmpty() - secondaryOptionError = sanitizedSecondary.isEmpty() + var hasOptionError = false + val resolvedOptions = if (requiresOptions) { + if (optionStates.size < 2) { + optionCountError = true + hasOptionError = true + } + optionStates.mapIndexed { index, option -> + val trimmedLabel = option.label.trim() + val trimmedBgm = option.bgmAsset.trim() + val deltaText = option.affectionDelta.trim() + val deltaValue = deltaText.toFloatOrNull() + + var updated = option + if (trimmedLabel.isEmpty()) { + updated = updated.copy(labelError = true) + hasOptionError = true + } else if (option.labelError) { + updated = updated.copy(labelError = false) + } + if (trimmedBgm.isEmpty()) { + updated = updated.copy(bgmError = true) + hasOptionError = true + } else if (option.bgmError) { + updated = updated.copy(bgmError = false) + } + if (deltaValue == null) { + updated = updated.copy(affectionError = true) + hasOptionError = true + } else if (option.affectionError) { + updated = updated.copy(affectionError = false) + } + if (updated != option) { + optionStates[index] = updated + } + + TriggerOption( + id = option.id, + label = trimmedLabel.ifEmpty { option.label }, + bgmAsset = trimmedBgm.ifEmpty { option.bgmAsset }, + affectionDelta = deltaValue ?: 0f + ) + } } else { - primaryOptionError = false - secondaryOptionError = false + emptyList() } - val optionsValid = !requiresOptions || (!primaryOptionError && !secondaryOptionError) - if (!keywordError && optionsValid) { - onConfirm( - sanitizedKeyword, - dialogType, - sanitizedPrimary.ifEmpty { primaryOption }, - sanitizedSecondary.ifEmpty { secondaryOption } - ) + if (!keywordError && !hasOptionError) { + optionCountError = false + onConfirm(sanitizedKeyword, dialogType, resolvedOptions) } } ) { @@ -320,6 +355,104 @@ private fun AddOrEditTriggerDialog( ) } +private data class OptionFormState( + val id: String = java.util.UUID.randomUUID().toString(), + val label: String, + val bgmAsset: String, + val affectionDelta: String, + val labelError: Boolean = false, + val bgmError: Boolean = false, + val affectionError: Boolean = false +) + +@Composable +private fun OptionEditor( + index: Int, + totalCount: Int, + state: OptionFormState, + onStateChange: (OptionFormState) -> Unit, + onRemove: () -> Unit +) { + Card( + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.4f)), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("选项 ${index + 1}", style = MaterialTheme.typography.titleSmall) + IconButton( + onClick = onRemove, + enabled = totalCount > 2, + colors = IconButtonDefaults.iconButtonColors( + contentColor = MaterialTheme.colorScheme.error, + disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + ) { + Icon(Icons.Default.Delete, contentDescription = "删除选项") + } + } + + OutlinedTextField( + value = state.label, + onValueChange = { onStateChange(state.copy(label = it, labelError = false)) }, + label = { Text("选项文本") }, + isError = state.labelError, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + if (state.labelError) { + Text( + "选项文本不能为空", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall + ) + } + + Spacer(Modifier.height(8.dp)) + + OutlinedTextField( + value = state.bgmAsset, + onValueChange = { onStateChange(state.copy(bgmAsset = it, bgmError = false)) }, + label = { Text("BGM 文件名") }, + isError = state.bgmError, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + if (state.bgmError) { + Text( + "BGM 文件名不能为空", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall + ) + } + + Spacer(Modifier.height(8.dp)) + + OutlinedTextField( + value = state.affectionDelta, + onValueChange = { onStateChange(state.copy(affectionDelta = it, affectionError = false)) }, + label = { Text("好感度变动 (可为负数)") }, + isError = state.affectionError, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + modifier = Modifier.fillMaxWidth() + ) + if (state.affectionError) { + Text( + "请输入有效的数字", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall + ) + } + } + } +} + @Composable fun ParentComposable() { var triggers by remember { diff --git a/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt b/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt index cbcdcd0..21e6b4e 100644 --- a/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt +++ b/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt @@ -5,14 +5,16 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -20,20 +22,21 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.tooling.preview.Preview +import com.example.livegg1.model.DialogType +import com.example.livegg1.model.TriggerDefaults +import com.example.livegg1.model.TriggerOption @Composable @OptIn(ExperimentalMaterial3Api::class) fun KeywordDialog( - primaryOptionLabel: String, - secondaryOptionLabel: String, - onPrimarySelected: () -> Unit, - onSecondarySelected: () -> Unit, - onDismiss: () -> Unit, - onSelectBgm: (String) -> Unit + options: List, + onOptionSelected: (TriggerOption) -> Unit, + onDismiss: () -> Unit ) { val dialogShape = RoundedCornerShape(16.dp) val dialogPink = Color(0xCCFFC0CB) val borderColor = Color(0xFFFF80AB) + val scrollState = rememberScrollState() BasicAlertDialog(onDismissRequest = onDismiss) { Card( @@ -58,38 +61,23 @@ fun KeywordDialog( Column( modifier = Modifier .padding(top = 24.dp) - .fillMaxWidth(), + .fillMaxWidth() + .verticalScroll(scrollState), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - OutlinedButton( - modifier = Modifier.fillMaxWidth(), - onClick = { - onPrimarySelected() - onSelectBgm("casual.mp3") - }, - shape = dialogShape, - border = BorderStroke(2.dp, borderColor), - colors = ButtonDefaults.outlinedButtonColors( - containerColor = Color.White, - contentColor = borderColor - ) - ) { - Text(primaryOptionLabel) - } - OutlinedButton( - modifier = Modifier.fillMaxWidth(), - onClick = { - onSecondarySelected() - onSelectBgm("Ah.mp3") - }, - shape = dialogShape, - border = BorderStroke(2.dp, borderColor), - colors = ButtonDefaults.outlinedButtonColors( - containerColor = Color.White, - contentColor = borderColor - ) - ) { - Text(secondaryOptionLabel) + options.forEach { option -> + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { onOptionSelected(option) }, + shape = dialogShape, + border = BorderStroke(2.dp, borderColor), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = Color.White, + contentColor = borderColor + ) + ) { + Text(option.label) + } } } } @@ -101,11 +89,8 @@ fun KeywordDialog( @Composable private fun KeywordDialogPreview() { KeywordDialog( - primaryOptionLabel = "好啊好啊", - secondaryOptionLabel = "不了", - onPrimarySelected = {}, - onSecondarySelected = {}, - onDismiss = {}, - onSelectBgm = {} + options = TriggerDefaults.defaultOptions(DialogType.CHOICE_DIALOG), + onOptionSelected = {}, + onDismiss = {} ) } diff --git a/app/src/main/java/com/example/livegg1/model/Trigger.kt b/app/src/main/java/com/example/livegg1/model/Trigger.kt index 89c4f48..e480023 100644 --- a/app/src/main/java/com/example/livegg1/model/Trigger.kt +++ b/app/src/main/java/com/example/livegg1/model/Trigger.kt @@ -8,11 +8,26 @@ enum class DialogType { // 未来可以扩展更多类型, 例如 INFO_DIALOG, INPUT_DIALOG 等 } +data class TriggerOption( + val id: String = UUID.randomUUID().toString(), + val label: String, + val bgmAsset: String, + val affectionDelta: Float +) + // 数据类,用于存储一个完整的触发器规则 data class KeywordTrigger( val id: String = UUID.randomUUID().toString(), // 使用UUID确保唯一性 val keyword: String, val dialogType: DialogType, - val primaryOptionText: String = "好啊好啊", - val secondaryOptionText: String = "不了" + val options: List = TriggerDefaults.defaultOptions(dialogType) ) + +object TriggerDefaults { + fun defaultOptions(dialogType: DialogType): List = when (dialogType) { + DialogType.CHOICE_DIALOG -> listOf( + TriggerOption(label = "好啊好啊", bgmAsset = "casual.mp3", affectionDelta = 0.4f), + TriggerOption(label = "不了", bgmAsset = "Ah.mp3", affectionDelta = -0.4f) + ) + } +} From d3df0619095684d4d159260cc5a0289e3e3dd612 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 11:48:47 +0800 Subject: [PATCH 007/147] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=99=BD=E5=B1=8F?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/livegg1/MainActivity.kt | 17 ++++++-- .../livegg1/dialog/TriggerManagementDialog.kt | 26 +++++++++++-- .../java/com/example/livegg1/model/Trigger.kt | 7 ++-- .../com/example/livegg1/ui/CameraScreen.kt | 39 +++++++++++++++++-- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/example/livegg1/MainActivity.kt b/app/src/main/java/com/example/livegg1/MainActivity.kt index d03f9c8..7fe4fae 100644 --- a/app/src/main/java/com/example/livegg1/MainActivity.kt +++ b/app/src/main/java/com/example/livegg1/MainActivity.kt @@ -94,12 +94,17 @@ class MainActivity : ComponentActivity() { var activeTrigger by remember { mutableStateOf(null) } var affectionEventId by remember { mutableLongStateOf(0L) } var affectionEventDelta by remember { mutableStateOf(0f) } + var whiteFlashEventId by remember { mutableLongStateOf(0L) } fun queueAffectionChange(delta: Float) { affectionEventDelta = delta affectionEventId++ } + fun triggerWhiteFlash() { + whiteFlashEventId++ + } + fun restartListeningIfPossible() { if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { speechListener.startListening() @@ -149,12 +154,13 @@ class MainActivity : ComponentActivity() { }, isDialogVisible = showKeywordDialog || showTriggerDialog, idleBgmAsset = idleBgmAsset, - onManageTriggers = { + onManageTriggers = { speechListener.stopListening() showTriggerDialog = true - }, - affectionEventId = affectionEventId, - affectionEventDelta = affectionEventDelta + }, + affectionEventId = affectionEventId, + affectionEventDelta = affectionEventDelta, + whiteFlashEventId = whiteFlashEventId ) if (showKeywordDialog) { @@ -168,6 +174,9 @@ class MainActivity : ComponentActivity() { ) idleBgmAsset = option.bgmAsset queueAffectionChange(option.affectionDelta) + if (option.triggerWhiteFlash) { + triggerWhiteFlash() + } showKeywordDialog = false activeTrigger = null if (!showTriggerDialog) { diff --git a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt index cbde454..b7e1eaf 100644 --- a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt +++ b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt @@ -140,7 +140,7 @@ private fun TriggerItem( if (trigger.dialogType == DialogType.CHOICE_DIALOG) { trigger.options.forEachIndexed { index, option -> Text( - text = "选项${index + 1}: \"${option.label}\" | BGM: ${option.bgmAsset} | Δ好感: ${option.affectionDelta}", + text = "选项${index + 1}: \"${option.label}\" | BGM: ${option.bgmAsset} | Δ好感: ${option.affectionDelta} | 白屏: ${if (option.triggerWhiteFlash) "是" else "否"}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -178,7 +178,8 @@ private fun AddOrEditTriggerDialog( id = option.id, label = option.label, bgmAsset = option.bgmAsset, - affectionDelta = option.affectionDelta.toString() + affectionDelta = option.affectionDelta.toString(), + triggerWhiteFlash = option.triggerWhiteFlash ) ) } @@ -274,7 +275,8 @@ private fun AddOrEditTriggerDialog( OptionFormState( label = "选项${optionStates.size + 1}", bgmAsset = "", - affectionDelta = "0.0" + affectionDelta = "0.0", + triggerWhiteFlash = false ) ) optionCountError = false @@ -331,7 +333,8 @@ private fun AddOrEditTriggerDialog( id = option.id, label = trimmedLabel.ifEmpty { option.label }, bgmAsset = trimmedBgm.ifEmpty { option.bgmAsset }, - affectionDelta = deltaValue ?: 0f + affectionDelta = deltaValue ?: 0f, + triggerWhiteFlash = option.triggerWhiteFlash ) } } else { @@ -360,6 +363,7 @@ private data class OptionFormState( val label: String, val bgmAsset: String, val affectionDelta: String, + val triggerWhiteFlash: Boolean = false, val labelError: Boolean = false, val bgmError: Boolean = false, val affectionError: Boolean = false @@ -433,6 +437,20 @@ private fun OptionEditor( Spacer(Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text("触发白屏", style = MaterialTheme.typography.bodyMedium) + Switch( + checked = state.triggerWhiteFlash, + onCheckedChange = { onStateChange(state.copy(triggerWhiteFlash = it)) } + ) + } + + Spacer(Modifier.height(8.dp)) + OutlinedTextField( value = state.affectionDelta, onValueChange = { onStateChange(state.copy(affectionDelta = it, affectionError = false)) }, diff --git a/app/src/main/java/com/example/livegg1/model/Trigger.kt b/app/src/main/java/com/example/livegg1/model/Trigger.kt index e480023..0d72b6c 100644 --- a/app/src/main/java/com/example/livegg1/model/Trigger.kt +++ b/app/src/main/java/com/example/livegg1/model/Trigger.kt @@ -12,7 +12,8 @@ data class TriggerOption( val id: String = UUID.randomUUID().toString(), val label: String, val bgmAsset: String, - val affectionDelta: Float + val affectionDelta: Float, + val triggerWhiteFlash: Boolean = false ) // 数据类,用于存储一个完整的触发器规则 @@ -26,8 +27,8 @@ data class KeywordTrigger( object TriggerDefaults { fun defaultOptions(dialogType: DialogType): List = when (dialogType) { DialogType.CHOICE_DIALOG -> listOf( - TriggerOption(label = "好啊好啊", bgmAsset = "casual.mp3", affectionDelta = 0.4f), - TriggerOption(label = "不了", bgmAsset = "Ah.mp3", affectionDelta = -0.4f) + TriggerOption(label = "好啊好啊", bgmAsset = "casual.mp3", affectionDelta = 0.4f, triggerWhiteFlash = false), + TriggerOption(label = "不了", bgmAsset = "Ah.mp3", affectionDelta = -0.4f, triggerWhiteFlash = false) ) } } diff --git a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt index ada28b1..fb769b1 100644 --- a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt +++ b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt @@ -114,7 +114,8 @@ fun CameraScreen( idleBgmAsset: String = "bgm.mp3", onManageTriggers: () -> Unit = {}, affectionEventId: Long = 0L, - affectionEventDelta: Float = 0f + affectionEventDelta: Float = 0f, + whiteFlashEventId: Long = 0L ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current @@ -142,6 +143,7 @@ fun CameraScreen( val max = 2f / 3f mutableStateOf(min + Random.nextFloat() * (max - min)) } + var flashAlpha by remember { mutableStateOf(0f) } LaunchedEffect(isInForeground) { if (!isInForeground) return@LaunchedEffect @@ -158,6 +160,23 @@ fun CameraScreen( affectionLevel = (affectionLevel + affectionEventDelta).coerceIn(0f, 1f) } } + + LaunchedEffect(whiteFlashEventId) { + if (whiteFlashEventId > 0L) { + // Quick double-flash: 0.3s on, 0.5s off, 0.3s on. + flashAlpha = 1f + delay(300) + flashAlpha = 0f + delay(500) + flashAlpha = 1f + delay(300) + flashAlpha = 0f + delay(600) + flashAlpha = 1f + delay(300) + flashAlpha = 0f + } + } // 好感度提高速度 fun bumpAffection() { coroutineScope.launch { @@ -442,7 +461,8 @@ fun CameraScreen( previewView = { AndroidView({ previewView }, modifier = Modifier.fillMaxSize()) }, affectionLevel = affectionLevel, onSaveSnapshot = ::saveCurrentScreen, - onManageTriggers = onManageTriggers + onManageTriggers = onManageTriggers, + flashAlpha = flashAlpha ) } @@ -459,7 +479,8 @@ private fun CameraScreenContent( previewView: @Composable () -> Unit, affectionLevel: Float, onSaveSnapshot: (String) -> Unit = {}, - onManageTriggers: () -> Unit = {} + onManageTriggers: () -> Unit = {}, + flashAlpha: Float = 0f ) { val context = LocalContext.current val hasChapterDrawable = remember(context) { @@ -764,6 +785,15 @@ private fun CameraScreenContent( } } } + + if (flashAlpha > 0f) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.White.copy(alpha = flashAlpha)) + .zIndex(10f) + ) + } } } @@ -839,7 +869,8 @@ fun CameraScreenPreview() { }, affectionLevel = 0.55f, onSaveSnapshot = {}, - onManageTriggers = {} + onManageTriggers = {}, + flashAlpha = 0f ) } } From cdd082596a6c118e48bd2a34f6c7b302ee70090f Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 12:04:38 +0800 Subject: [PATCH 008/147] =?UTF-8?q?=E8=A7=A6=E5=8F=91=E5=99=A8=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E4=BF=AE=E6=94=B9=E5=92=8C=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../livegg1/dialog/TriggerManagementDialog.kt | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt index b7e1eaf..9ab419d 100644 --- a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt +++ b/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt @@ -111,6 +111,10 @@ fun TriggerManagementDialog( } showAddOrEditDialog = null }, + onDelete = { toDelete -> + onDeleteTrigger(toDelete) + showAddOrEditDialog = null + }, onDismiss = { showAddOrEditDialog = null } ) } @@ -130,11 +134,13 @@ private fun TriggerItem( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween + .padding(start = 12.dp, end = 4.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically ) { - Column { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { Text(text = "关键词: \"${trigger.keyword}\"", fontWeight = FontWeight.SemiBold) Text(text = "触发弹窗: ${trigger.dialogType.name}", style = MaterialTheme.typography.bodySmall) if (trigger.dialogType == DialogType.CHOICE_DIALOG) { @@ -147,7 +153,10 @@ private fun TriggerItem( } } } - Row { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { IconButton(onClick = { onEdit(trigger) }) { Icon(Icons.Default.Edit, contentDescription = "编辑", tint = MaterialTheme.colorScheme.primary) } @@ -165,6 +174,7 @@ private fun AddOrEditTriggerDialog( trigger: KeywordTrigger?, isEditing: Boolean, onConfirm: (keyword: String, dialogType: DialogType, options: List) -> Unit, + onDelete: (KeywordTrigger) -> Unit, onDismiss: () -> Unit ) { var keyword by remember(trigger?.id) { mutableStateOf(trigger?.keyword ?: "") } @@ -286,6 +296,25 @@ private fun AddOrEditTriggerDialog( Text("添加选项", modifier = Modifier.padding(start = 4.dp)) } } + + if (isEditing && trigger != null) { + Spacer(Modifier.height(24.dp)) + Divider() + Spacer(Modifier.height(16.dp)) + OutlinedButton( + onClick = { + onDelete(trigger) + onDismiss() + }, + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.error + ), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.error.copy(alpha = 0.6f)), + modifier = Modifier.fillMaxWidth() + ) { + Text("删除该触发器") + } + } } }, confirmButton = { From f4a14afe050d1c347721ca1a0c4109937f7fed5b Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 12:36:47 +0800 Subject: [PATCH 009/147] =?UTF-8?q?=E7=82=B9=E5=87=BBimg5=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E6=94=B9=E6=88=90=E5=AE=9E=E6=97=B6=E8=A7=86=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/livegg1/ui/CameraScreen.kt | 102 +++++++++++++----- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt index fb769b1..f152fc4 100644 --- a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt +++ b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt @@ -137,6 +137,7 @@ fun CameraScreen( val updateIntervalMs = 3500L // 每次更新间隔(毫秒),可根据需要调整为 6000L var progress by remember { mutableStateOf(0f) } // 0f 开始,逐渐增长到 1f var timeRemainingSec by remember { mutableStateOf(updateIntervalMs / 1000f) } + var isRealtimePreview by remember { mutableStateOf(false) } var affectionLevel by remember { val min = 1f / 3f @@ -204,6 +205,22 @@ fun CameraScreen( } } + fun enableRealtimePreview() { + if (isRealtimePreview) return + Log.d("CameraScreen", "Switching to realtime preview") + isRealtimePreview = true + progress = 1f + timeRemainingSec = 0f + val bitmapToRecycle = lastBitmap ?: imageToShow + bitmapToRecycle?.let { candidate -> + if (!candidate.isRecycled) { + candidate.recycle() + } + } + lastBitmap = null + imageToShow = null + } + // 新的状态管理:用于连续识别 val recognizedSentences = remember { mutableStateListOf() } var currentPartialText by remember { mutableStateOf("") } @@ -404,17 +421,38 @@ fun CameraScreen( } // --- 核心逻辑:定时拍照更新背景 --- - LaunchedEffect(imageCapture, isInForeground) { + LaunchedEffect(imageCapture, isInForeground, isRealtimePreview) { if (!isInForeground) { progress = 0f timeRemainingSec = updateIntervalMs / 1000f return@LaunchedEffect } - // 首次启动时,先拍一张照片作为背景 - takePhoto(imageCapture, cameraExecutor, { imageToShow = cropBitmapToAspectRatio(it, screenAspectRatio) }, {}) - delay(1000) - // 主循环:每 updateIntervalMs 拍照一次。内部按 100ms 步进更新进度和剩余时间。 + val shouldCaptureStills = !isRealtimePreview + + if (shouldCaptureStills) { + // 首次启动时,先拍一张照片作为背景 + takePhoto( + imageCapture, + cameraExecutor, + { bitmap -> + if (isRealtimePreview) { + if (!bitmap.isRecycled) { + bitmap.recycle() + } + } else { + imageToShow = cropBitmapToAspectRatio(bitmap, screenAspectRatio) + } + }, + {} + ) + delay(1000) + } else { + progress = 0f + timeRemainingSec = updateIntervalMs / 1000f + } + + // 主循环:每 updateIntervalMs 更新一次。内部按 100ms 步进更新进度和剩余时间。 val stepMs = 100L while (isActive) { var elapsed = 0L @@ -426,25 +464,34 @@ fun CameraScreen( timeRemainingSec = remaining / 1000f } - // 时间到,拍照并重置状态 - takePhoto( - imageCapture = imageCapture, - executor = cameraExecutor, - onImageCaptured = { newBitmap -> - val croppedBitmap = cropBitmapToAspectRatio(newBitmap, screenAspectRatio) - lastBitmap?.let { oldBitmap -> - if (oldBitmap != croppedBitmap && !oldBitmap.isRecycled) { - oldBitmap.recycle() + if (!isActive) break + + if (shouldCaptureStills) { + takePhoto( + imageCapture = imageCapture, + executor = cameraExecutor, + onImageCaptured = { newBitmap -> + if (isRealtimePreview) { + if (!newBitmap.isRecycled) { + newBitmap.recycle() + } + } else { + val croppedBitmap = cropBitmapToAspectRatio(newBitmap, screenAspectRatio) + lastBitmap?.let { oldBitmap -> + if (oldBitmap != croppedBitmap && !oldBitmap.isRecycled) { + oldBitmap.recycle() + } + } + lastBitmap = croppedBitmap + imageToShow = croppedBitmap + Log.d("MainLoop", "Background photo updated.") } - } - lastBitmap = croppedBitmap - imageToShow = croppedBitmap - Log.d("MainLoop", "Background photo updated.") - }, - onError = { Log.e("MainLoop", "Photo capture failed", it) } - ) + }, + onError = { Log.e("MainLoop", "Photo capture failed", it) } + ) + } - // 拍照后马上重置进度(下一刻开始倒计时) + // 重置进度(下一刻开始倒计时) progress = 0f timeRemainingSec = updateIntervalMs / 1000f } @@ -457,11 +504,13 @@ fun CameraScreen( captionToShow = captionToShow, progress = progress, timeRemainingSec = timeRemainingSec, + isRealtimePreview = isRealtimePreview, chapterTitle = chapterTitle, previewView = { AndroidView({ previewView }, modifier = Modifier.fillMaxSize()) }, affectionLevel = affectionLevel, onSaveSnapshot = ::saveCurrentScreen, onManageTriggers = onManageTriggers, + onEnableRealtimePreview = ::enableRealtimePreview, flashAlpha = flashAlpha ) } @@ -473,6 +522,7 @@ private fun CameraScreenContent( captionToShow: String, progress: Float, timeRemainingSec: Float, + isRealtimePreview: Boolean = false, rectOffsetX: Dp = 4.dp, rectOffsetY: Dp = 10.dp, chapterTitle: String, @@ -480,6 +530,7 @@ private fun CameraScreenContent( affectionLevel: Float, onSaveSnapshot: (String) -> Unit = {}, onManageTriggers: () -> Unit = {}, + onEnableRealtimePreview: () -> Unit = {}, flashAlpha: Float = 0f ) { val context = LocalContext.current @@ -602,7 +653,7 @@ private fun CameraScreenContent( contentScale = ContentScale.Crop ) } - else -> { + else -> if (!isRealtimePreview) { Box(modifier = Modifier.fillMaxSize().background(Color.Black)) } } @@ -770,7 +821,10 @@ private fun CameraScreenContent( TextButton(onClick = { Log.d("CameraScreen", "image4 clicked") }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { Image(painter = painterResource(id = R.drawable.image4), contentDescription = "image4", modifier = Modifier.width(18.dp).height(18.dp), colorFilter = ColorFilter.tint(Color.White)) } - TextButton(onClick = { Log.d("CameraScreen", "image5 clicked") }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { + TextButton(onClick = { + Log.d("CameraScreen", "image5 clicked") + onEnableRealtimePreview() + }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { Image(painter = painterResource(id = R.drawable.image5), contentDescription = "image5", modifier = Modifier.width(18.dp).height(18.dp), colorFilter = ColorFilter.tint(Color.White)) } TextButton(onClick = { Log.d("CameraScreen", "image7 clicked") }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { From 1a3a4ee4c4878150bca9f3255ed9dd41d24e46f0 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 12:40:38 +0800 Subject: [PATCH 010/147] =?UTF-8?q?=E5=86=8D=E6=AC=A1=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=8F=AF=E4=BB=A5=E5=88=87=E6=8D=A2=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/livegg1/ui/CameraScreen.kt | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt index f152fc4..a310ef8 100644 --- a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt +++ b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt @@ -205,20 +205,26 @@ fun CameraScreen( } } - fun enableRealtimePreview() { - if (isRealtimePreview) return - Log.d("CameraScreen", "Switching to realtime preview") - isRealtimePreview = true - progress = 1f - timeRemainingSec = 0f - val bitmapToRecycle = lastBitmap ?: imageToShow - bitmapToRecycle?.let { candidate -> - if (!candidate.isRecycled) { - candidate.recycle() + fun togglePreviewMode() { + if (isRealtimePreview) { + Log.d("CameraScreen", "Switching to timed preview") + isRealtimePreview = false + progress = 0f + timeRemainingSec = updateIntervalMs / 1000f + } else { + Log.d("CameraScreen", "Switching to realtime preview") + isRealtimePreview = true + progress = 1f + timeRemainingSec = 0f + val bitmapToRecycle = lastBitmap ?: imageToShow + bitmapToRecycle?.let { candidate -> + if (!candidate.isRecycled) { + candidate.recycle() + } } + lastBitmap = null + imageToShow = null } - lastBitmap = null - imageToShow = null } // 新的状态管理:用于连续识别 @@ -510,7 +516,7 @@ fun CameraScreen( affectionLevel = affectionLevel, onSaveSnapshot = ::saveCurrentScreen, onManageTriggers = onManageTriggers, - onEnableRealtimePreview = ::enableRealtimePreview, + onTogglePreviewMode = ::togglePreviewMode, flashAlpha = flashAlpha ) } @@ -530,7 +536,7 @@ private fun CameraScreenContent( affectionLevel: Float, onSaveSnapshot: (String) -> Unit = {}, onManageTriggers: () -> Unit = {}, - onEnableRealtimePreview: () -> Unit = {}, + onTogglePreviewMode: () -> Unit = {}, flashAlpha: Float = 0f ) { val context = LocalContext.current @@ -823,9 +829,15 @@ private fun CameraScreenContent( } TextButton(onClick = { Log.d("CameraScreen", "image5 clicked") - onEnableRealtimePreview() + onTogglePreviewMode() }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { - Image(painter = painterResource(id = R.drawable.image5), contentDescription = "image5", modifier = Modifier.width(18.dp).height(18.dp), colorFilter = ColorFilter.tint(Color.White)) + val realtimeTint = if (isRealtimePreview) Color(0xFFFFC857) else Color.White + Image( + painter = painterResource(id = R.drawable.image5), + contentDescription = "image5", + modifier = Modifier.width(18.dp).height(18.dp), + colorFilter = ColorFilter.tint(realtimeTint) + ) } TextButton(onClick = { Log.d("CameraScreen", "image7 clicked") }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { Image(painter = painterResource(id = R.drawable.image7), contentDescription = "image7", modifier = Modifier.width(18.dp).height(18.dp), colorFilter = ColorFilter.tint(Color.White)) From aa119c7267c9c14d150addab88fe056f634157a9 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 13:01:06 +0800 Subject: [PATCH 011/147] =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=88=87=E6=8D=A2CG?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=9F=E5=BA=A6=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/livegg1/ui/CameraScreen.kt | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt index a310ef8..912476b 100644 --- a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt +++ b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt @@ -30,6 +30,8 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.TextButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.Text +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Slider import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -100,6 +102,7 @@ import android.os.Build import android.provider.MediaStore import android.view.View import com.example.livegg1.R +import kotlin.math.roundToInt import kotlin.random.Random import kotlin.math.min import kotlin.math.max @@ -134,10 +137,22 @@ fun CameraScreen( var isInForeground by remember { mutableStateOf(true) } // 进度条相关:显示距离下一次更新的剩余时间 - val updateIntervalMs = 3500L // 每次更新间隔(毫秒),可根据需要调整为 6000L + var updateIntervalMs by remember { mutableStateOf(3500L) } // 每次更新间隔(毫秒) var progress by remember { mutableStateOf(0f) } // 0f 开始,逐渐增长到 1f var timeRemainingSec by remember { mutableStateOf(updateIntervalMs / 1000f) } + var isIntervalDialogVisible by remember { mutableStateOf(false) } + var pendingIntervalSec by remember { mutableStateOf(updateIntervalMs / 1000f) } var isRealtimePreview by remember { mutableStateOf(false) } + val minIntervalSec = 1f + val maxIntervalSec = 10f + + LaunchedEffect(updateIntervalMs) { + val clamped = (updateIntervalMs / 1000f).coerceIn(minIntervalSec, maxIntervalSec) + pendingIntervalSec = clamped + if (!isRealtimePreview) { + timeRemainingSec = clamped + } + } var affectionLevel by remember { val min = 1f / 3f @@ -427,7 +442,7 @@ fun CameraScreen( } // --- 核心逻辑:定时拍照更新背景 --- - LaunchedEffect(imageCapture, isInForeground, isRealtimePreview) { + LaunchedEffect(imageCapture, isInForeground, isRealtimePreview, updateIntervalMs) { if (!isInForeground) { progress = 0f timeRemainingSec = updateIntervalMs / 1000f @@ -504,6 +519,44 @@ fun CameraScreen( } // --- UI 界面 --- + + if (isIntervalDialogVisible) { + AlertDialog( + onDismissRequest = { isIntervalDialogVisible = false }, + title = { Text("调整更新间隔") }, + text = { + Column { + Text("拖动滑块来设定画面更新的间隔时间") + Spacer(modifier = Modifier.height(16.dp)) + Slider( + value = pendingIntervalSec, + onValueChange = { pendingIntervalSec = it.coerceIn(minIntervalSec, maxIntervalSec) }, + valueRange = minIntervalSec..maxIntervalSec + ) + Spacer(modifier = Modifier.height(8.dp)) + Text(String.format("%.1f 秒", pendingIntervalSec)) + } + }, + confirmButton = { + TextButton(onClick = { + val clamped = pendingIntervalSec.coerceIn(minIntervalSec, maxIntervalSec) + val newIntervalMs = (clamped * 1000f).roundToInt().toLong() + updateIntervalMs = newIntervalMs + progress = 0f + timeRemainingSec = newIntervalMs / 1000f + isIntervalDialogVisible = false + }) { + Text("确定") + } + }, + dismissButton = { + TextButton(onClick = { isIntervalDialogVisible = false }) { + Text("取消") + } + } + ) + } + CameraScreenContent( isLoading = isLoading, imageToShow = imageToShow, @@ -516,6 +569,10 @@ fun CameraScreen( affectionLevel = affectionLevel, onSaveSnapshot = ::saveCurrentScreen, onManageTriggers = onManageTriggers, + onAdjustInterval = { + pendingIntervalSec = (updateIntervalMs / 1000f).coerceIn(minIntervalSec, maxIntervalSec) + isIntervalDialogVisible = true + }, onTogglePreviewMode = ::togglePreviewMode, flashAlpha = flashAlpha ) @@ -536,6 +593,7 @@ private fun CameraScreenContent( affectionLevel: Float, onSaveSnapshot: (String) -> Unit = {}, onManageTriggers: () -> Unit = {}, + onAdjustInterval: () -> Unit = {}, onTogglePreviewMode: () -> Unit = {}, flashAlpha: Float = 0f ) { @@ -824,7 +882,10 @@ private fun CameraScreenContent( } // 新增的图像按钮集合:image4, image5, image7, image8 - TextButton(onClick = { Log.d("CameraScreen", "image4 clicked") }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { + TextButton(onClick = { + Log.d("CameraScreen", "image4 clicked") + onAdjustInterval() + }, contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp), modifier = Modifier.height(btnHeight)) { Image(painter = painterResource(id = R.drawable.image4), contentDescription = "image4", modifier = Modifier.width(18.dp).height(18.dp), colorFilter = ColorFilter.tint(Color.White)) } TextButton(onClick = { From bae4542d872f5d906cb6c08fdd272c1a93642ac2 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 13:04:51 +0800 Subject: [PATCH 012/147] =?UTF-8?q?=E6=9B=B4=E6=96=B0README=EF=BC=8C?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E4=BA=86=E4=B8=80=E4=BA=9B=E6=96=B0=E7=89=B9?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++-- app/src/main/java/com/example/livegg1/ui/CameraScreen.kt | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8260e13..3a2361a 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,18 @@ 作为整活向APP,目前没有接入AI,可以离线运行 -### 画面卡顿是为了复刻GalGame切换CG的效果,后续会开发可以手动切换更新帧率的功能。 +### 画面卡顿是为了复刻GalGame切换CG的效果,可以手动调整更新速度或者切换成流畅的普通画面 APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载,报毒是正常状况,请放心使用 ## 使用教程 -点击齿轮按钮可以设置触发弹窗的关键词,SAVE和Q.SAVE可以保存当前截屏到手机。好感度条会随着时间慢慢减少,有语音的时候会不断提高,选择不同的弹窗选项也会影响好感度。 +- 点击齿轮按钮可以设置触发弹窗的关键词 +- SAVE和Q.SAVE可以保存当前截屏到手机 +- 好感度条会随着时间慢慢减少,有语音的时候会不断提高,选择不同的弹窗选项也会影响好感度。 +- 点击快进可以调整CG效果画面的更新速度 +- 点击跳过可以把画面更新切换成实时的 image diff --git a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt index 912476b..3219da5 100644 --- a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt +++ b/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt @@ -523,7 +523,7 @@ fun CameraScreen( if (isIntervalDialogVisible) { AlertDialog( onDismissRequest = { isIntervalDialogVisible = false }, - title = { Text("调整更新间隔") }, + title = { Text("调整CG更新间隔") }, text = { Column { Text("拖动滑块来设定画面更新的间隔时间") From aef27f58e1fc868f3e254751e2fd194d998f2b67 Mon Sep 17 00:00:00 2001 From: JamesLuo <2823659087@qq.com> Date: Tue, 4 Nov 2025 13:11:11 +0800 Subject: [PATCH 013/147] ready for v1.8.0 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ababb2..0254d52 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "com.example.livegg1" minSdk = 24 targetSdk = 36 - versionCode = 1 - versionName = "1.6.1" + versionCode = 2 + versionName = "1.8.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } From 33a64e533e4ee6650283bea3018e419c8b1015a4 Mon Sep 17 00:00:00 2001 From: Coconut-Aero <148566655+Coconut-Aero@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:34:32 +0800 Subject: [PATCH 014/147] feat. Package Name - before: com.example.livegg1 - now: com.jstone.livegalgame It didn't pass tests. So don't release it --- .gitignore | 3 ++- app/build.gradle.kts | 4 ++-- .../ExampleInstrumentedTest.kt | 4 ++-- .../livegalgame}/MainActivity.kt | 16 ++++++++-------- .../livegg1 => jstone/livegalgame}/Utils.kt | 2 +- .../dialog/TriggerManagementDialog.kt | 10 +++++----- .../livegalgame}/dialog/keywordDialog.kt | 8 ++++---- .../livegalgame}/model/DialogType.kt | 2 +- .../livegalgame}/model/KeywordTrigger.kt | 2 +- .../livegalgame}/model/Trigger.kt | 2 +- .../livegalgame}/speech/KeywordSpeechListener.kt | 4 ++-- .../livegalgame}/ui/CameraScreen.kt | 10 +++++----- .../livegalgame}/ui/theme/Color.kt | 2 +- .../livegalgame}/ui/theme/Theme.kt | 2 +- .../livegalgame}/ui/theme/Type.kt | 2 +- .../{livegg1 => livegalgame}/ExampleUnitTest.kt | 2 +- 16 files changed, 38 insertions(+), 37 deletions(-) rename app/src/androidTest/java/com/example/{livegg1 => livegalgame}/ExampleInstrumentedTest.kt (84%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/MainActivity.kt (96%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/Utils.kt (98%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/dialog/TriggerManagementDialog.kt (98%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/dialog/keywordDialog.kt (93%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/model/DialogType.kt (72%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/model/KeywordTrigger.kt (71%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/model/Trigger.kt (96%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/speech/KeywordSpeechListener.kt (94%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/ui/CameraScreen.kt (99%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/ui/theme/Color.kt (86%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/ui/theme/Theme.kt (97%) rename app/src/main/java/com/{example/livegg1 => jstone/livegalgame}/ui/theme/Type.kt (95%) rename app/src/test/java/com/example/{livegg1 => livegalgame}/ExampleUnitTest.kt (91%) diff --git a/.gitignore b/.gitignore index 3ab81ff..c7be6c8 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ local.properties # Signing properties signing.properties /release.keystore -/release \ No newline at end of file +/release +.vscode \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0254d52..2a4ed81 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,11 +8,11 @@ plugins { } android { - namespace = "com.example.livegg1" + namespace = "com.jstone.livegalgame" compileSdk = 36 defaultConfig { - applicationId = "com.example.livegg1" + applicationId = "com.jstone.livegalgame" minSdk = 24 targetSdk = 36 versionCode = 2 diff --git a/app/src/androidTest/java/com/example/livegg1/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt similarity index 84% rename from app/src/androidTest/java/com/example/livegg1/ExampleInstrumentedTest.kt rename to app/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt index a764525..8c121b9 100644 --- a/app/src/androidTest/java/com/example/livegg1/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package com.example.livegg1 +package com.jstone.livegalgame import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.example.livegg1", appContext.packageName) + assertEquals("com.jstone.livegalgame", appContext.packageName) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/livegg1/MainActivity.kt b/app/src/main/java/com/jstone/livegalgame/MainActivity.kt similarity index 96% rename from app/src/main/java/com/example/livegg1/MainActivity.kt rename to app/src/main/java/com/jstone/livegalgame/MainActivity.kt index 7fe4fae..442d5c8 100644 --- a/app/src/main/java/com/example/livegg1/MainActivity.kt +++ b/app/src/main/java/com/jstone/livegalgame/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.livegg1 +package com.jstone.livegalgame import android.Manifest import android.content.pm.PackageManager @@ -25,13 +25,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import kotlinx.coroutines.flow.collect -import com.example.livegg1.dialog.KeywordDialog -import com.example.livegg1.dialog.TriggerManagementDialog -import com.example.livegg1.model.DialogType -import com.example.livegg1.model.KeywordTrigger -import com.example.livegg1.speech.KeywordSpeechListener -import com.example.livegg1.ui.CameraScreen -import com.example.livegg1.ui.theme.LiveGG1Theme +import com.jstone.livegalgame.dialog.KeywordDialog +import com.jstone.livegalgame.dialog.TriggerManagementDialog +import com.jstone.livegalgame.model.DialogType +import com.jstone.livegalgame.model.KeywordTrigger +import com.jstone.livegalgame.speech.KeywordSpeechListener +import com.jstone.livegalgame.ui.CameraScreen +import com.jstone.livegalgame.ui.theme.LiveGG1Theme import java.util.concurrent.ExecutorService import java.util.concurrent.Executors diff --git a/app/src/main/java/com/example/livegg1/Utils.kt b/app/src/main/java/com/jstone/livegalgame/Utils.kt similarity index 98% rename from app/src/main/java/com/example/livegg1/Utils.kt rename to app/src/main/java/com/jstone/livegalgame/Utils.kt index 2a96c33..d4aff37 100644 --- a/app/src/main/java/com/example/livegg1/Utils.kt +++ b/app/src/main/java/com/jstone/livegalgame/Utils.kt @@ -1,4 +1,4 @@ -package com.example.livegg1 +package com.jstone.livegalgame import android.graphics.Bitmap import android.graphics.BitmapFactory diff --git a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt b/app/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt similarity index 98% rename from app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt rename to app/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt index 9ab419d..cbb5b08 100644 --- a/app/src/main/java/com/example/livegg1/dialog/TriggerManagementDialog.kt +++ b/app/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt @@ -1,4 +1,4 @@ -package com.example.livegg1.dialog +package com.jstone.livegalgame.dialog import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.* @@ -21,10 +21,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog -import com.example.livegg1.model.DialogType -import com.example.livegg1.model.KeywordTrigger -import com.example.livegg1.model.TriggerDefaults -import com.example.livegg1.model.TriggerOption +import com.jstone.livegalgame.model.DialogType +import com.jstone.livegalgame.model.KeywordTrigger +import com.jstone.livegalgame.model.TriggerDefaults +import com.jstone.livegalgame.model.TriggerOption @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt b/app/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt similarity index 93% rename from app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt rename to app/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt index 21e6b4e..0266d15 100644 --- a/app/src/main/java/com/example/livegg1/dialog/keywordDialog.kt +++ b/app/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt @@ -1,4 +1,4 @@ -package com.example.livegg1.dialog +package com.jstone.livegalgame.dialog import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement @@ -22,9 +22,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.tooling.preview.Preview -import com.example.livegg1.model.DialogType -import com.example.livegg1.model.TriggerDefaults -import com.example.livegg1.model.TriggerOption +import com.jstone.livegalgame.model.DialogType +import com.jstone.livegalgame.model.TriggerDefaults +import com.jstone.livegalgame.model.TriggerOption @Composable @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/java/com/example/livegg1/model/DialogType.kt b/app/src/main/java/com/jstone/livegalgame/model/DialogType.kt similarity index 72% rename from app/src/main/java/com/example/livegg1/model/DialogType.kt rename to app/src/main/java/com/jstone/livegalgame/model/DialogType.kt index 8ab2cec..c825bdd 100644 --- a/app/src/main/java/com/example/livegg1/model/DialogType.kt +++ b/app/src/main/java/com/jstone/livegalgame/model/DialogType.kt @@ -1,3 +1,3 @@ -package com.example.livegg1.model +package com.jstone.livegalgame.model // Placeholder file retained to avoid redeclaration; see Trigger.kt for DialogType definition. diff --git a/app/src/main/java/com/example/livegg1/model/KeywordTrigger.kt b/app/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt similarity index 71% rename from app/src/main/java/com/example/livegg1/model/KeywordTrigger.kt rename to app/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt index 82fb40c..f162373 100644 --- a/app/src/main/java/com/example/livegg1/model/KeywordTrigger.kt +++ b/app/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt @@ -1,3 +1,3 @@ -package com.example.livegg1.model +package com.jstone.livegalgame.model // Intentionally left blank; KeywordTrigger is defined alongside DialogType in Trigger.kt. diff --git a/app/src/main/java/com/example/livegg1/model/Trigger.kt b/app/src/main/java/com/jstone/livegalgame/model/Trigger.kt similarity index 96% rename from app/src/main/java/com/example/livegg1/model/Trigger.kt rename to app/src/main/java/com/jstone/livegalgame/model/Trigger.kt index 0d72b6c..22abdec 100644 --- a/app/src/main/java/com/example/livegg1/model/Trigger.kt +++ b/app/src/main/java/com/jstone/livegalgame/model/Trigger.kt @@ -1,4 +1,4 @@ -package com.example.livegg1.model +package com.jstone.livegalgame.model import java.util.UUID diff --git a/app/src/main/java/com/example/livegg1/speech/KeywordSpeechListener.kt b/app/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt similarity index 94% rename from app/src/main/java/com/example/livegg1/speech/KeywordSpeechListener.kt rename to app/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt index ae6986c..d1913e1 100644 --- a/app/src/main/java/com/example/livegg1/speech/KeywordSpeechListener.kt +++ b/app/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt @@ -1,6 +1,6 @@ -package com.example.livegg1.speech +package com.jstone.livegalgame.speech -import com.example.livegg1.model.KeywordTrigger +import com.jstone.livegalgame.model.KeywordTrigger import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow diff --git a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt b/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt similarity index 99% rename from app/src/main/java/com/example/livegg1/ui/CameraScreen.kt rename to app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt index 3219da5..dc85e12 100644 --- a/app/src/main/java/com/example/livegg1/ui/CameraScreen.kt +++ b/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt @@ -1,4 +1,4 @@ -package com.example.livegg1.ui +package com.jstone.livegalgame.ui import android.content.ContentValues import android.content.Context @@ -77,9 +77,9 @@ import androidx.compose.foundation.layout.offset import androidx.compose.ui.zIndex import androidx.compose.ui.draw.clip // ...existing imports -import com.example.livegg1.Utils.cropBitmapToAspectRatio -import com.example.livegg1.Utils.takePhoto -import com.example.livegg1.ui.theme.LiveGG1Theme +import com.jstone.livegalgame.Utils.cropBitmapToAspectRatio +import com.jstone.livegalgame.Utils.takePhoto +import com.jstone.livegalgame.ui.theme.LiveGG1Theme import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.delay @@ -101,7 +101,7 @@ import java.lang.IllegalStateException import android.os.Build import android.provider.MediaStore import android.view.View -import com.example.livegg1.R +import com.jstone.livegalgame.R import kotlin.math.roundToInt import kotlin.random.Random import kotlin.math.min diff --git a/app/src/main/java/com/example/livegg1/ui/theme/Color.kt b/app/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt similarity index 86% rename from app/src/main/java/com/example/livegg1/ui/theme/Color.kt rename to app/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt index 8ce9e67..01a4952 100644 --- a/app/src/main/java/com/example/livegg1/ui/theme/Color.kt +++ b/app/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt @@ -1,4 +1,4 @@ -package com.example.livegg1.ui.theme +package com.jstone.livegalgame.ui.theme import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/com/example/livegg1/ui/theme/Theme.kt b/app/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt similarity index 97% rename from app/src/main/java/com/example/livegg1/ui/theme/Theme.kt rename to app/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt index 4857e4d..f2f6616 100644 --- a/app/src/main/java/com/example/livegg1/ui/theme/Theme.kt +++ b/app/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt @@ -1,4 +1,4 @@ -package com.example.livegg1.ui.theme +package com.jstone.livegalgame.ui.theme import android.app.Activity import android.os.Build diff --git a/app/src/main/java/com/example/livegg1/ui/theme/Type.kt b/app/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt similarity index 95% rename from app/src/main/java/com/example/livegg1/ui/theme/Type.kt rename to app/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt index d7aaa23..7bfe658 100644 --- a/app/src/main/java/com/example/livegg1/ui/theme/Type.kt +++ b/app/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt @@ -1,4 +1,4 @@ -package com.example.livegg1.ui.theme +package com.jstone.livegalgame.ui.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle diff --git a/app/src/test/java/com/example/livegg1/ExampleUnitTest.kt b/app/src/test/java/com/example/livegalgame/ExampleUnitTest.kt similarity index 91% rename from app/src/test/java/com/example/livegg1/ExampleUnitTest.kt rename to app/src/test/java/com/example/livegalgame/ExampleUnitTest.kt index 4911911..5d0a008 100644 --- a/app/src/test/java/com/example/livegg1/ExampleUnitTest.kt +++ b/app/src/test/java/com/example/livegalgame/ExampleUnitTest.kt @@ -1,4 +1,4 @@ -package com.example.livegg1 +package com.jstone.livegalgame import org.junit.Test From a017db05876a513337382ec8829eabda73313229 Mon Sep 17 00:00:00 2001 From: JStone2934 <91839415+JStone2934@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:09:00 +0800 Subject: [PATCH 015/147] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 3a2361a..ce9fcd9 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载 - 点击快进可以调整CG效果画面的更新速度 - 点击跳过可以把画面更新切换成实时的 +### B站视频:[【-修复了GalGame玩家和女生聊天没有字幕的Bug-】](https://www.bilibili.com/video/BV15Q1jBQEzq/?share_source=copy_web&vd_source=181e7acfd50ad37dbfacd601ca302c13) image @@ -26,6 +27,9 @@ APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载 CSDN下载链接:https://download.csdn.net/download/qq_63533710/92237453 +网盘链接:通过网盘分享的文件:LiveGG +链接: https://pan.baidu.com/s/1Bpt2DZNvjzT6BpKr8RyG-A?pwd=94g6 提取码: 94g6 + ## 本地构建指南 1) 前提条件 From fe737e6f31b6d74a9de74f1a0689f7617ecd1dc3 Mon Sep 17 00:00:00 2001 From: JStone2934 <91839415+JStone2934@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:04:09 +0800 Subject: [PATCH 016/147] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ce9fcd9..ff941e7 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,10 @@ APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载 - 点击齿轮按钮可以设置触发弹窗的关键词 - SAVE和Q.SAVE可以保存当前截屏到手机 -- 好感度条会随着时间慢慢减少,有语音的时候会不断提高,选择不同的弹窗选项也会影响好感度。 +- 好感度条会随着时间慢慢减少,有语音的时候会不断提高,选择不同的弹窗选项也会影响好感度 - 点击快进可以调整CG效果画面的更新速度 - 点击跳过可以把画面更新切换成实时的 +- 如遇初始化失败,可以试试重启APP,或者在手机设置里面把软件的语音权限打开再关闭 ### B站视频:[【-修复了GalGame玩家和女生聊天没有字幕的Bug-】](https://www.bilibili.com/video/BV15Q1jBQEzq/?share_source=copy_web&vd_source=181e7acfd50ad37dbfacd601ca302c13) image From ad8ad09de1657491b3d5559e2ffe7defa9571fc7 Mon Sep 17 00:00:00 2001 From: chlorodose Date: Mon, 10 Nov 2025 15:54:12 +0800 Subject: [PATCH 017/147] =?UTF-8?q?fix(speech):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E9=87=8D=E8=AF=95=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E4=BA=86=E5=90=AF=E5=8A=A8=E5=BA=94=E7=94=A8=E6=97=B6?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=BA=A6=E5=85=8B=E9=A3=8E=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jstone/livegalgame/ui/CameraScreen.kt | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt b/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt index dc85e12..568d22d 100644 --- a/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt +++ b/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt @@ -351,21 +351,48 @@ fun CameraScreen( // --- 核心逻辑:初始化 --- LaunchedEffect(Unit) { - withContext(Dispatchers.IO) { - try { + val r = runCatching { + withContext(Dispatchers.IO) { val sourcePath = "model" val targetPath = StorageService.sync(context, sourcePath, "model") Log.d("Vosk", "Model sync completed. Target path: $targetPath") - model = Model(targetPath) - speechService = SpeechService(Recognizer(model, 16000.0f), 16000.0f) - Log.d("Vosk", "Model and SpeechService loaded successfully.") - } catch (e: IOException) { - Log.e("Vosk", "Failed to initialize Vosk.", e) - errorText = "错误: 初始化语音模型失败。" - } finally { + Model(targetPath) + } + }.onFailure { e -> + Log.e("Vosk", "Failed to initialize Vosk model.", e) + errorText = "错误: 初始化语音模型失败。" + } + + if (r.isFailure) { + isLoading = false + return@LaunchedEffect + } else { + model = r.getOrThrow() + } + + Log.d("Vosk", "Model loaded successfully.") + + // 系统拉起麦克风服务时会暂时占用设备,我们多重试几次 + val attempts = (1000 / 250) * 3 + repeat(attempts) { attempt -> + val r = runCatching { + withContext(Dispatchers.IO) { SpeechService(Recognizer(model, 16000.0f), 16000.0f) } + } + + if (r.isSuccess) { + speechService = r.getOrThrow() + Log.d("Vosk", "Speech service initialized.") isLoading = false + return@LaunchedEffect + } else { + Log.e("Vosk", "Speech service init attempt ${attempt + 1}/$attempts failed.", r.exceptionOrNull()) + delay(250) } } + + Log.e("Vosk", "Failed to initialize speech service after $attempts attempts.") + errorText = "错误: 初始化麦克风失败。" + isLoading = false } // --- 核心逻辑:绑定相机和资源管理 --- From 0fa1f9dab55e5b3fe04921303345db307b4296c8 Mon Sep 17 00:00:00 2001 From: chlorodose Date: Mon, 10 Nov 2025 16:01:31 +0800 Subject: [PATCH 018/147] =?UTF-8?q?docs:=20=E5=88=A0=E9=99=A4=E4=BA=86?= =?UTF-8?q?=E5=85=B3=E4=BA=8E=E5=B7=B2=E4=BF=AE=E5=A4=8D=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E7=9A=84=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ff941e7..86b201c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载 - 好感度条会随着时间慢慢减少,有语音的时候会不断提高,选择不同的弹窗选项也会影响好感度 - 点击快进可以调整CG效果画面的更新速度 - 点击跳过可以把画面更新切换成实时的 -- 如遇初始化失败,可以试试重启APP,或者在手机设置里面把软件的语音权限打开再关闭 ### B站视频:[【-修复了GalGame玩家和女生聊天没有字幕的Bug-】](https://www.bilibili.com/video/BV15Q1jBQEzq/?share_source=copy_web&vd_source=181e7acfd50ad37dbfacd601ca302c13) image From 3b0227b69591b3d205e15182097dd04ce9d51141 Mon Sep 17 00:00:00 2001 From: James <91839415+JStone2934@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:08:49 +0800 Subject: [PATCH 019/147] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 86b201c..b39dabc 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载 - 好感度条会随着时间慢慢减少,有语音的时候会不断提高,选择不同的弹窗选项也会影响好感度 - 点击快进可以调整CG效果画面的更新速度 - 点击跳过可以把画面更新切换成实时的 - +- 如遇初始化失败,可以试试重启APP,或者在手机设置里面把软件的语音权限打开再关闭 ### B站视频:[【-修复了GalGame玩家和女生聊天没有字幕的Bug-】](https://www.bilibili.com/video/BV15Q1jBQEzq/?share_source=copy_web&vd_source=181e7acfd50ad37dbfacd601ca302c13) image From 08d254bed547bac545f80fcbf9323c018b9e4e17 Mon Sep 17 00:00:00 2001 From: James <91839415+JStone2934@users.noreply.github.com> Date: Tue, 11 Nov 2025 16:57:28 +0800 Subject: [PATCH 020/147] Revise download section in README Updated download links and added new download sources. --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b39dabc..c7c4fb2 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,15 @@ APP在[tag](https://github.com/JStone2934/LiveGalGame/tags)中可以直接下载 ### B站视频:[【-修复了GalGame玩家和女生聊天没有字幕的Bug-】](https://www.bilibili.com/video/BV15Q1jBQEzq/?share_source=copy_web&vd_source=181e7acfd50ad37dbfacd601ca302c13) image +## 下载 -现在的软件在[tag](https://github.com/JStone2934/LiveGalGame/tags)下,功能更完整的版本会后续发布 +现在的软件在这里--->>[Release](https://github.com/JStone2934/LiveGalGame/tags)下载,功能更完整的版本会后续发布 + +夸克网盘:https://pan.quark.cn/s/1000136902b5 CSDN下载链接:https://download.csdn.net/download/qq_63533710/92237453 -网盘链接:通过网盘分享的文件:LiveGG +百度网盘:通过网盘分享的文件:LiveGG 链接: https://pan.baidu.com/s/1Bpt2DZNvjzT6BpKr8RyG-A?pwd=94g6 提取码: 94g6 ## 本地构建指南 From 525fae326856b196167da541069666803a1f736a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=A1=A0=E1=A0=B5=E1=A1=A0=E1=A1=B3=20=E1=A1=A0=E1=A0=B5?= =?UTF-8?q?=E1=A1=A0=20=E1=A0=AE=E1=A0=A0=E1=A0=A8=E1=A1=A9=E1=A0=8B?= =?UTF-8?q?=E1=A0=A0=E1=A0=A8?= <125150101+UjuiUjuMandan@users.noreply.github.com> Date: Thu, 13 Nov 2025 07:24:54 +0800 Subject: [PATCH 021/147] Remove DependencyInfoBlock for F-Droid --- app/build.gradle.kts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2a4ed81..b1c388c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -65,6 +65,12 @@ android { buildFeatures { compose = true } + dependenciesInfo { + // Disables dependency metadata when building APKs. + includeInApk = false + // Disables dependency metadata when building Android App Bundles. + includeInBundle = false + } } dependencies { @@ -95,4 +101,4 @@ dependencies { androidTestImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) -} \ No newline at end of file +} From 38b37bc50ab09700d52f4f11a87929f69167e5f6 Mon Sep 17 00:00:00 2001 From: James <91839415+JStone2934@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:50:58 +0800 Subject: [PATCH 022/147] Add QQ group for collaboration Added QQ group information for collaboration. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c7c4fb2..8ef2a6a 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,5 @@ CSDN下载链接:https://download.csdn.net/download/qq_63533710/92237453 ## 欢迎自由开发,有活你就直接往里加 + +QQ群:2068167850 From 1ab9ec117b9af7ea7aae5f5c8ee6f9e226687880 Mon Sep 17 00:00:00 2001 From: James <91839415+JStone2934@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:50:04 +0800 Subject: [PATCH 023/147] Update QQ group number in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ef2a6a..d7cb3e1 100644 --- a/README.md +++ b/README.md @@ -77,4 +77,4 @@ CSDN下载链接:https://download.csdn.net/download/qq_63533710/92237453 ## 欢迎自由开发,有活你就直接往里加 -QQ群:2068167850 +QQ群:1074602400 From 6d34b64c2a0d4f0555e07f54e6e591664b2733b2 Mon Sep 17 00:00:00 2001 From: chenspeculation Date: Wed, 12 Nov 2025 22:03:20 +0800 Subject: [PATCH 024/147] feat: reorganize project structure and add desktop development docs - Move mobile app files from root to mobile/ directory - Add comprehensive desktop development documentation - Remove .idea directory - Reorganize project structure for better separation --- .idea/.gitignore | 3 - .idea/.name | 1 - .idea/AndroidProjectSystem.xml | 6 - .idea/appInsightsSettings.xml | 26 - .idea/compiler.xml | 6 - .idea/deploymentTargetSelector.xml | 18 - .idea/gradle.xml | 19 - .idea/inspectionProfiles/Project_Default.xml | 61 - .idea/migrations.xml | 10 - .idea/misc.xml | 10 - .idea/runConfigurations.xml | 17 - .idea/vcs.xml | 6 - desktop/DEVELOPER_GUIDE.md | 1322 +++++++++++++++++ desktop/README.md | 39 + desktop/spec/UI_DESIGN_INDEX.md | 300 ++++ desktop/spec/audio-capture-tech-note.md | 54 + desktop/spec/build-and-release.md | 51 + desktop/spec/data-model.md | 58 + desktop/spec/hud-ux.md | 33 + desktop/spec/llm-integration.md | 34 + desktop/spec/prd-desktop.md | 60 + desktop/spec/privacy-and-permissions.md | 30 + desktop/spec/tech-architecture.md | 57 + desktop/spec/test-plan.md | 38 + desktop/spec/ui-design-01-chat-window.md | 460 ++++++ desktop/spec/ui-design-02-llm-config.md | 577 +++++++ desktop/spec/ui-design-03-dashboard.md | 557 +++++++ .../spec/ui-design-04-conversation-detail.md | 614 ++++++++ desktop/spec/ui-design-components.md | 1001 +++++++++++++ .../metadata/android/en-US/images/icon.png | 1 - .../.kotlin}/errors/errors-1759038853800.log | 0 LICENSE => mobile/LICENSE | 0 README.md => mobile/README.md | 0 {app => mobile/app}/.gitignore | 0 {app => mobile/app}/build.gradle.kts | 0 {app => mobile/app}/proguard-rules.pro | 0 .../livegalgame/ExampleInstrumentedTest.kt | 0 .../app}/src/main/AndroidManifest.xml | 0 {app => mobile/app}/src/main/assets/Ah.mp3 | Bin .../app}/src/main/assets/TeaBreak.mp3 | Bin {app => mobile/app}/src/main/assets/bgm.mp3 | Bin .../app}/src/main/assets/casual.mp3 | Bin {app => mobile/app}/src/main/assets/heart.svg | 0 .../app}/src/main/assets/image2.svg | 0 .../app}/src/main/assets/image3.svg | 0 .../app}/src/main/assets/image4.svg | 0 .../app}/src/main/assets/image4.xml | 0 .../app}/src/main/assets/image5.svg | 0 .../app}/src/main/assets/image7.svg | 0 .../app}/src/main/assets/image8.svg | 0 .../app}/src/main/assets/image9.svg | 0 .../app}/src/main/assets/model/README | 0 .../app}/src/main/assets/model/am/final.mdl | Bin .../app}/src/main/assets/model/conf/mfcc.conf | 0 .../src/main/assets/model/conf/model.conf | 0 .../app}/src/main/assets/model/graph/Gr.fst | Bin .../app}/src/main/assets/model/graph/HCLr.fst | Bin .../main/assets/model/graph/disambig_tid.int | 0 .../model/graph/phones/word_boundary.int | 0 .../src/main/assets/model/ivector/final.dubm | Bin .../src/main/assets/model/ivector/final.ie | Bin .../src/main/assets/model/ivector/final.mat | Bin .../assets/model/ivector/global_cmvn.stats | 0 .../assets/model/ivector/online_cmvn.conf | 0 .../src/main/assets/model/ivector/splice.conf | 0 .../app}/src/main/assets/model/uuid | 0 .../app}/src/main/ic_launcher-playstore.png | Bin .../com/jstone/livegalgame/MainActivity.kt | 0 .../main/java/com/jstone/livegalgame/Utils.kt | 0 .../dialog/TriggerManagementDialog.kt | 0 .../livegalgame/dialog/keywordDialog.kt | 0 .../jstone/livegalgame/model/DialogType.kt | 0 .../livegalgame/model/KeywordTrigger.kt | 0 .../com/jstone/livegalgame/model/Trigger.kt | 0 .../speech/KeywordSpeechListener.kt | 0 .../com/jstone/livegalgame/ui/CameraScreen.kt | 0 .../com/jstone/livegalgame/ui/theme/Color.kt | 0 .../com/jstone/livegalgame/ui/theme/Theme.kt | 0 .../com/jstone/livegalgame/ui/theme/Type.kt | 0 {app => mobile/app}/src/main/res/AppIcons.zip | Bin .../app}/src/main/res/drawable/chapter.png | Bin .../app}/src/main/res/drawable/chocake.png | Bin .../app}/src/main/res/drawable/heart.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../res/drawable/ic_launcher_foreground.xml | 0 .../app}/src/main/res/drawable/image2.xml | 0 .../app}/src/main/res/drawable/image3.xml | 0 .../app}/src/main/res/drawable/image4.xml | 0 .../app}/src/main/res/drawable/image5.xml | 0 .../app}/src/main/res/drawable/image7.xml | 0 .../app}/src/main/res/drawable/image8.xml | 0 .../app}/src/main/res/drawable/image9.xml | 0 .../app}/src/main/res/drawable/lemon.png | Bin .../app}/src/main/res/drawable/strbcake.png | Bin .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin .../mipmap-hdpi/ic_launcher_foreground.webp | Bin .../res/mipmap-hdpi/ic_launcher_round.webp | Bin .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin .../mipmap-mdpi/ic_launcher_foreground.webp | Bin .../res/mipmap-mdpi/ic_launcher_round.webp | Bin .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin .../ic_launcher_foreground.webp | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin .../app}/src/main/res/values/colors.xml | 0 .../res/values/ic_launcher_background.xml | 0 .../app}/src/main/res/values/strings.xml | 0 .../app}/src/main/res/values/themes.xml | 0 .../app}/src/main/res/xml/backup_rules.xml | 0 .../main/res/xml/data_extraction_rules.xml | 0 .../example/livegalgame/ExampleUnitTest.kt | 0 build.gradle.kts => mobile/build.gradle.kts | 0 .../android/en-US/full_description.txt | 0 .../metadata/android/en-US/images/icon.png | 1 + .../android/en-US/short_description.txt | 0 .../metadata/android/en-US/title.txt | 0 .../android/zh-CN/full_description.txt | 0 .../android/zh-CN/short_description.txt | 0 .../metadata/android/zh-CN/title.txt | 0 gradle.properties => mobile/gradle.properties | 0 {gradle => mobile/gradle}/libs.versions.toml | 0 .../gradle}/wrapper/gradle-wrapper.jar | Bin .../gradle}/wrapper/gradle-wrapper.properties | 0 gradlew => mobile/gradlew | 0 gradlew.bat => mobile/gradlew.bat | 0 ...s\344\270\255\345\217\221\345\270\203.txt" | 0 .../settings.gradle.kts | 0 134 files changed, 5286 insertions(+), 184 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/AndroidProjectSystem.xml delete mode 100644 .idea/appInsightsSettings.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/deploymentTargetSelector.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/migrations.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/runConfigurations.xml delete mode 100644 .idea/vcs.xml create mode 100644 desktop/DEVELOPER_GUIDE.md create mode 100644 desktop/README.md create mode 100644 desktop/spec/UI_DESIGN_INDEX.md create mode 100644 desktop/spec/audio-capture-tech-note.md create mode 100644 desktop/spec/build-and-release.md create mode 100644 desktop/spec/data-model.md create mode 100644 desktop/spec/hud-ux.md create mode 100644 desktop/spec/llm-integration.md create mode 100644 desktop/spec/prd-desktop.md create mode 100644 desktop/spec/privacy-and-permissions.md create mode 100644 desktop/spec/tech-architecture.md create mode 100644 desktop/spec/test-plan.md create mode 100644 desktop/spec/ui-design-01-chat-window.md create mode 100644 desktop/spec/ui-design-02-llm-config.md create mode 100644 desktop/spec/ui-design-03-dashboard.md create mode 100644 desktop/spec/ui-design-04-conversation-detail.md create mode 100644 desktop/spec/ui-design-components.md delete mode 120000 fastlane/metadata/android/en-US/images/icon.png rename {.kotlin => mobile/.kotlin}/errors/errors-1759038853800.log (100%) rename LICENSE => mobile/LICENSE (100%) rename README.md => mobile/README.md (100%) rename {app => mobile/app}/.gitignore (100%) rename {app => mobile/app}/build.gradle.kts (100%) rename {app => mobile/app}/proguard-rules.pro (100%) rename {app => mobile/app}/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt (100%) rename {app => mobile/app}/src/main/AndroidManifest.xml (100%) rename {app => mobile/app}/src/main/assets/Ah.mp3 (100%) rename {app => mobile/app}/src/main/assets/TeaBreak.mp3 (100%) rename {app => mobile/app}/src/main/assets/bgm.mp3 (100%) rename {app => mobile/app}/src/main/assets/casual.mp3 (100%) rename {app => mobile/app}/src/main/assets/heart.svg (100%) rename {app => mobile/app}/src/main/assets/image2.svg (100%) rename {app => mobile/app}/src/main/assets/image3.svg (100%) rename {app => mobile/app}/src/main/assets/image4.svg (100%) rename {app => mobile/app}/src/main/assets/image4.xml (100%) rename {app => mobile/app}/src/main/assets/image5.svg (100%) rename {app => mobile/app}/src/main/assets/image7.svg (100%) rename {app => mobile/app}/src/main/assets/image8.svg (100%) rename {app => mobile/app}/src/main/assets/image9.svg (100%) rename {app => mobile/app}/src/main/assets/model/README (100%) rename {app => mobile/app}/src/main/assets/model/am/final.mdl (100%) rename {app => mobile/app}/src/main/assets/model/conf/mfcc.conf (100%) rename {app => mobile/app}/src/main/assets/model/conf/model.conf (100%) rename {app => mobile/app}/src/main/assets/model/graph/Gr.fst (100%) rename {app => mobile/app}/src/main/assets/model/graph/HCLr.fst (100%) rename {app => mobile/app}/src/main/assets/model/graph/disambig_tid.int (100%) rename {app => mobile/app}/src/main/assets/model/graph/phones/word_boundary.int (100%) rename {app => mobile/app}/src/main/assets/model/ivector/final.dubm (100%) rename {app => mobile/app}/src/main/assets/model/ivector/final.ie (100%) rename {app => mobile/app}/src/main/assets/model/ivector/final.mat (100%) rename {app => mobile/app}/src/main/assets/model/ivector/global_cmvn.stats (100%) rename {app => mobile/app}/src/main/assets/model/ivector/online_cmvn.conf (100%) rename {app => mobile/app}/src/main/assets/model/ivector/splice.conf (100%) rename {app => mobile/app}/src/main/assets/model/uuid (100%) rename {app => mobile/app}/src/main/ic_launcher-playstore.png (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/MainActivity.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/Utils.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/model/DialogType.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/model/Trigger.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt (100%) rename {app => mobile/app}/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt (100%) rename {app => mobile/app}/src/main/res/AppIcons.zip (100%) rename {app => mobile/app}/src/main/res/drawable/chapter.png (100%) rename {app => mobile/app}/src/main/res/drawable/chocake.png (100%) rename {app => mobile/app}/src/main/res/drawable/heart.xml (100%) rename {app => mobile/app}/src/main/res/drawable/ic_launcher_background.xml (100%) rename {app => mobile/app}/src/main/res/drawable/ic_launcher_foreground.xml (100%) rename {app => mobile/app}/src/main/res/drawable/image2.xml (100%) rename {app => mobile/app}/src/main/res/drawable/image3.xml (100%) rename {app => mobile/app}/src/main/res/drawable/image4.xml (100%) rename {app => mobile/app}/src/main/res/drawable/image5.xml (100%) rename {app => mobile/app}/src/main/res/drawable/image7.xml (100%) rename {app => mobile/app}/src/main/res/drawable/image8.xml (100%) rename {app => mobile/app}/src/main/res/drawable/image9.xml (100%) rename {app => mobile/app}/src/main/res/drawable/lemon.png (100%) rename {app => mobile/app}/src/main/res/drawable/strbcake.png (100%) rename {app => mobile/app}/src/main/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {app => mobile/app}/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {app => mobile/app}/src/main/res/mipmap-hdpi/ic_launcher.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-hdpi/ic_launcher_round.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-mdpi/ic_launcher.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-mdpi/ic_launcher_round.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xhdpi/ic_launcher.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xhdpi/ic_launcher_round.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xxhdpi/ic_launcher.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xxxhdpi/ic_launcher.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp (100%) rename {app => mobile/app}/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp (100%) rename {app => mobile/app}/src/main/res/values/colors.xml (100%) rename {app => mobile/app}/src/main/res/values/ic_launcher_background.xml (100%) rename {app => mobile/app}/src/main/res/values/strings.xml (100%) rename {app => mobile/app}/src/main/res/values/themes.xml (100%) rename {app => mobile/app}/src/main/res/xml/backup_rules.xml (100%) rename {app => mobile/app}/src/main/res/xml/data_extraction_rules.xml (100%) rename {app => mobile/app}/src/test/java/com/example/livegalgame/ExampleUnitTest.kt (100%) rename build.gradle.kts => mobile/build.gradle.kts (100%) rename {fastlane => mobile/fastlane}/metadata/android/en-US/full_description.txt (100%) create mode 100644 mobile/fastlane/metadata/android/en-US/images/icon.png rename {fastlane => mobile/fastlane}/metadata/android/en-US/short_description.txt (100%) rename {fastlane => mobile/fastlane}/metadata/android/en-US/title.txt (100%) rename {fastlane => mobile/fastlane}/metadata/android/zh-CN/full_description.txt (100%) rename {fastlane => mobile/fastlane}/metadata/android/zh-CN/short_description.txt (100%) rename {fastlane => mobile/fastlane}/metadata/android/zh-CN/title.txt (100%) rename gradle.properties => mobile/gradle.properties (100%) rename {gradle => mobile/gradle}/libs.versions.toml (100%) rename {gradle => mobile/gradle}/wrapper/gradle-wrapper.jar (100%) rename {gradle => mobile/gradle}/wrapper/gradle-wrapper.properties (100%) rename gradlew => mobile/gradlew (100%) rename gradlew.bat => mobile/gradlew.bat (100%) rename "release/\345\220\216\347\273\255apk\345\234\250tags\344\270\255\345\217\221\345\270\203.txt" => "mobile/release/\345\220\216\347\273\255apk\345\234\250tags\344\270\255\345\217\221\345\270\203.txt" (100%) rename settings.gradle.kts => mobile/settings.gradle.kts (100%) diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 4a84fcc..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -proguard-rules.pro \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml deleted file mode 100644 index 4a53bee..0000000 --- a/.idea/AndroidProjectSystem.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml deleted file mode 100644 index 371f2e2..0000000 --- a/.idea/appInsightsSettings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b86273d..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml deleted file mode 100644 index f9c8c6b..0000000 --- a/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 639c779..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 7061a0d..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml deleted file mode 100644 index f8051a6..0000000 --- a/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 74dd639..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 16660f1..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/desktop/DEVELOPER_GUIDE.md b/desktop/DEVELOPER_GUIDE.md new file mode 100644 index 0000000..cd74b2a --- /dev/null +++ b/desktop/DEVELOPER_GUIDE.md @@ -0,0 +1,1322 @@ +# 🚀 LiveGalGame 桌面版开发者快速启动指南 + +> **目标**:从零开始搭建 Electron 应用,完整实现所有功能 +> **预计时间**:4-6 周(取决于团队规模和并行度) +> **技术栈**:Electron + React + TypeScript + Tailwind CSS + SQLite + +--- + +## 📍 项目阶段概览 + +``` +第一阶段(第 1 周):项目初始化与环境搭建 + ↓ +第二阶段(第 1-2 周):组件库与基础设施 + ↓ +第三阶段(第 2-3 周):核心功能开发(音频、LLM、数据) + ↓ +第四阶段(第 3-4 周):页面与业务逻辑集成 + ↓ +第五阶段(第 4-5 周):打包、测试与优化 + ↓ +第六阶段(第 5-6 周):发版与迭代 +``` + +--- + +# 第一阶段:项目初始化与环境搭建(第 1 周) + +## 1.1 环境检查清单 + +在开始前,确保已安装: + +```bash +# 检查 Node.js 版本(建议 18.x+) +node --version # 应为 v18.0.0 或更高 + +# 检查 npm 版本(建议 9.x+) +npm --version + +# 检查 Git +git --version +``` + +如未安装,请访问: +- Node.js:https://nodejs.org +- Git:https://git-scm.com + +## 1.2 初始化项目结构 + +### 第一步:克隆或创建项目 + +```bash +cd ~/LiveGalGame +mkdir desktop +cd desktop + +# 初始化 npm 项目 +npm init -y +``` + +### 第二步:安装核心依赖 + +```bash +npm install \ + electron \ + react \ + react-dom \ + typescript \ + tailwindcss \ + postcss \ + autoprefixer \ + framer-motion \ + zustand \ + @react-query/core \ + axios +``` + +### 第三步:安装开发依赖 + +```bash +npm install -D \ + @types/react \ + @types/react-dom \ + @types/node \ + ts-loader \ + webpack \ + webpack-cli \ + webpack-dev-server \ + html-webpack-plugin \ + @testing-library/react \ + @testing-library/jest-dom \ + jest \ + @storybook/react \ + @storybook/addon-essentials \ + electron-builder \ + cross-env +``` + +### 第四步:创建基础目录结构 + +```bash +# 从项目根目录执行 +mkdir -p src/{main,renderer,components,pages,hooks,store,types,utils,assets} +mkdir -p public +mkdir -p spec +mkdir -p tests +mkdir -p .github/workflows +``` + +最终结构: + +``` +desktop/ +├── src/ +│ ├── main/ +│ │ ├── index.ts # Electron 主进程入口 +│ │ ├── preload.ts # 预加载脚本(安全桥接) +│ │ └── ipc/ # IPC 通信处理器 +│ │ +│ ├── renderer/ +│ │ ├── index.tsx # React 入口 +│ │ └── App.tsx # 根组件 +│ │ +│ ├── components/ +│ │ ├── base/ # 基础组件(Button, Input 等) +│ │ ├── containers/ # 容器组件(Layout, Sidebar 等) +│ │ └── features/ # 业务组件(MessageBubble 等) +│ │ +│ ├── pages/ +│ │ ├── ChatWindow.tsx # 对话 HUD 浮窗 +│ │ ├── Dashboard.tsx # 主页 +│ │ ├── LLMConfig.tsx # LLM 配置 +│ │ └── ConversationDetail.tsx # 对话详情 +│ │ +│ ├── hooks/ +│ │ ├── useTheme.ts # 主题 hook +│ │ ├── useUIStore.ts # UI 状态 hook +│ │ └── useConversation.ts # 对话数据 hook +│ │ +│ ├── store/ +│ │ ├── ui.ts # UI 状态(Zustand) +│ │ ├── conversation.ts # 对话状态 +│ │ └── config.ts # 应用配置 +│ │ +│ ├── types/ +│ │ ├── index.ts # 全局类型定义 +│ │ ├── conversation.ts # 对话相关类型 +│ │ └── llm.ts # LLM 相关类型 +│ │ +│ ├── utils/ +│ │ ├── logger.ts # 日志工具 +│ │ ├── storage.ts # 本地存储 +│ │ ├── api.ts # API 请求 +│ │ └── formatters.ts # 格式化工具 +│ │ +│ ├── assets/ +│ │ ├── icons/ +│ │ ├── fonts/ +│ │ └── styles/ +│ │ ├── globals.css # 全局样式 +│ │ ├── tailwind.css # Tailwind 入口 +│ │ └── animations.css # 自定义动画 +│ │ +│ └── main.css # 应用主样式 +│ +├── public/ +│ ├── index.html # 渲染进程 HTML +│ └── preload.js # 预加载脚本分发 +│ +├── spec/ +│ ├── prd-desktop.md # ← 产品需求文档 +│ ├── tech-architecture.md # ← 技术架构 +│ ├── ui-design-01-chat-window.md # ← UI 设计 1 +│ ├── ui-design-02-llm-config.md # ← UI 设计 2 +│ ├── ui-design-03-dashboard.md # ← UI 设计 3 +│ ├── ui-design-04-conversation-detail.md # ← UI 设计 4 +│ ├── ui-design-components.md # ← 组件库规范 +│ ├── audio-capture-tech-note.md # ← 音频采集技术 +│ ├── llm-integration.md # ← LLM 集成 +│ ├── hud-ux.md # ← HUD 交互细节 +│ ├── data-model.md # ← 数据模型 +│ ├── build-and-release.md # ← 构建发版 +│ ├── privacy-and-permissions.md # ← 隐私权限 +│ └── test-plan.md # ← 测试计划 +│ +├── tests/ +│ ├── unit/ +│ ├── integration/ +│ └── e2e/ +│ +├── .github/ +│ └── workflows/ +│ ├── build.yml +│ └── release.yml +│ +├── package.json +├── tsconfig.json # TypeScript 配置 +├── webpack.config.js # Webpack 配置 +├── tailwind.config.js # Tailwind 配置 +├── postcss.config.js # PostCSS 配置 +├── jest.config.js # Jest 配置 +├── electron-builder.yml # 打包配置 +├── README.md +└── DEVELOPER_GUIDE.md # ← 本文档 +``` + +## 1.3 配置文件创建 + +### 1.3.1 tsconfig.json + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@pages/*": ["src/pages/*"], + "@hooks/*": ["src/hooks/*"], + "@store/*": ["src/store/*"], + "@types/*": ["src/types/*"], + "@utils/*": ["src/utils/*"] + } + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "tests"] +} +``` + +### 1.3.2 tailwind.config.js + +```javascript +module.exports = { + content: ["./src/**/*.{js,jsx,ts,tsx}"], + theme: { + extend: { + colors: { + brand: { + primary: "#D91B5C", + dark: "#C2185B", + light: "rgba(217, 27, 92, 0.1)", + }, + }, + spacing: { + xs: "4px", + sm: "8px", + md: "16px", + lg: "24px", + xl: "32px", + "2xl": "40px", + }, + borderRadius: { + xs: "4px", + sm: "6px", + md: "8px", + lg: "12px", + xl: "16px", + }, + }, + }, + plugins: [], +}; +``` + +### 1.3.3 package.json scripts + +```json +{ + "scripts": { + "start": "cross-env NODE_ENV=development electron .", + "dev": "concurrently \"npm run webpack:dev\" \"wait-on http://localhost:8080 && npm run start\"", + "webpack:dev": "webpack serve --config webpack.config.js --mode development", + "build": "webpack --config webpack.config.js --mode production", + "test": "jest", + "test:watch": "jest --watch", + "storybook": "storybook dev -p 6006", + "storybook:build": "storybook build", + "lint": "eslint src", + "package": "electron-builder", + "package:win": "electron-builder --win", + "package:mac": "electron-builder --mac", + "release": "npm run build && electron-builder -p always" + } +} +``` + +## 1.4 验证环境 + +```bash +# 检查依赖安装成功 +npm list electron react typescript + +# 尝试编译 +npm run build + +# 应该看到 "build successful" 消息 +``` + +## 1.5 参考文档 + +| 文档 | 用途 | +|------|------| +| **README.md** | 项目概览和快速开始 | +| **tech-architecture.md** | 了解 Electron 主/渲染进程分层 | +| **build-and-release.md** | 理解打包流程(虽然现在还不需要) | + +--- + +# 第二阶段:组件库与基础设施(第 1-2 周) + +## 2.1 创建设计系统与主题 + +### 第一步:创建设计令牌文件 + +**文件**:`src/assets/styles/tokens.ts` + +```typescript +export const colors = { + // 品牌色 + brand: { + primary: "#D91B5C", + primaryDark: "#C2185B", + primaryLight: "rgba(217, 27, 92, 0.1)", + }, + // 中性色 + gray: { + 50: "#F9FAFB", + 100: "#F3F4F6", + 200: "#E5E7EB", + 300: "#D1D5DB", + 400: "#9CA3AF", + 500: "#6B7280", + 600: "#4B5563", + 700: "#374151", + 800: "#1F2937", + 900: "#111827", + }, + // 语义色 + status: { + success: "#10B981", + warning: "#F59E0B", + error: "#EF4444", + info: "#3B82F6", + }, +}; + +export const spacing = { + xs: 4, + sm: 8, + md: 16, + lg: 24, + xl: 32, + "2xl": 40, +}; + +export const radius = { + xs: 4, + sm: 6, + md: 8, + lg: 12, + xl: 16, + full: 9999, +}; + +export const shadows = { + xs: "0 1px 2px rgba(0, 0, 0, 0.05)", + sm: "0 1px 3px rgba(0, 0, 0, 0.1)", + md: "0 4px 6px rgba(0, 0, 0, 0.1)", + lg: "0 10px 15px rgba(0, 0, 0, 0.15)", + xl: "0 10px 40px rgba(0, 0, 0, 0.2)", +}; +``` + +### 第二步:创建全局样式 + +**文件**:`src/assets/styles/globals.css` + +```css +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&display=swap'); + +:root { + --color-brand-primary: #D91B5C; + --color-brand-primary-dark: #C2185B; + --color-brand-primary-light: rgba(217, 27, 92, 0.1); + + --color-gray-50: #F9FAFB; + --color-gray-100: #F3F4F6; + --color-gray-200: #E5E7EB; + --color-gray-300: #D1D5DB; + --color-gray-400: #9CA3AF; + --color-gray-500: #6B7280; + --color-gray-600: #4B5563; + --color-gray-700: #374151; + --color-gray-800: #1F2937; + + --color-success: #10B981; + --color-warning: #F59E0B; + --color-error: #EF4444; + --color-info: #3B82F6; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Noto Sans SC', system-ui, -apple-system, sans-serif; + background-color: #F5F7FA; + color: #1F2937; + line-height: 1.5; +} + +button { + cursor: pointer; + border: none; + font-family: inherit; +} + +input, textarea { + font-family: inherit; +} + +/* 自定义滚动条 */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: #D1D5DB; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #9CA3AF; +} +``` + +### 第三步:创建 Framer Motion 动画预设 + +**文件**:`src/utils/animations.ts` + +```typescript +import { Variants } from 'framer-motion'; + +export const fadeInOut: Variants = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, + transition: { duration: 0.2 }, +}; + +export const slideUp: Variants = { + initial: { y: 20, opacity: 0 }, + animate: { y: 0, opacity: 1 }, + exit: { y: 20, opacity: 0 }, + transition: { duration: 0.3, ease: 'easeOut' }, +}; + +export const slideInRight: Variants = { + initial: { x: 300, opacity: 0 }, + animate: { x: 0, opacity: 1 }, + exit: { x: 300, opacity: 0 }, + transition: { duration: 0.3, ease: 'easeOut' }, +}; + +export const scaleIn: Variants = { + initial: { scale: 0.9, opacity: 0 }, + animate: { scale: 1, opacity: 1 }, + exit: { scale: 0.9, opacity: 0 }, + transition: { duration: 0.2, ease: 'easeOut' }, +}; + +export const container = { + hidden: { opacity: 0 }, + show: { + opacity: 1, + transition: { + staggerChildren: 0.05, + }, + }, +}; + +export const item = { + hidden: { opacity: 0, y: 20 }, + show: { + opacity: 1, + y: 0, + transition: { duration: 0.3 }, + }, +}; +``` + +## 2.2 创建基础组件库 + +### 任务列表 +- [ ] 创建 Button 组件(`src/components/base/Button.tsx`) +- [ ] 创建 Input 组件(`src/components/base/Input.tsx`) +- [ ] 创建 Card 组件(`src/components/base/Card.tsx`) +- [ ] 创建 Badge 组件(`src/components/base/Badge.tsx`) +- [ ] 创建 Modal 组件(`src/components/base/Modal.tsx`) +- [ ] 创建 Spinner 组件(`src/components/base/Spinner.tsx`) + +**参考文档**:`spec/ui-design-components.md` 第 2 节 + +**示例代码**:`src/components/base/Button.tsx` + +```typescript +import React from 'react'; +import clsx from 'clsx'; + +interface ButtonProps { + variant?: 'primary' | 'secondary' | 'outline' | 'ghost'; + size?: 'xs' | 'sm' | 'md' | 'lg'; + disabled?: boolean; + loading?: boolean; + fullWidth?: boolean; + children: React.ReactNode; + onClick?: () => void; + className?: string; + type?: 'button' | 'submit' | 'reset'; +} + +export const Button: React.FC = ({ + variant = 'primary', + size = 'md', + disabled = false, + loading = false, + fullWidth = false, + children, + onClick, + className, + type = 'button', +}) => { + const variantClasses = { + primary: 'bg-brand-primary text-white hover:bg-brand-dark disabled:opacity-50', + secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200 border border-gray-200', + outline: 'bg-transparent text-gray-800 border border-gray-300 hover:bg-gray-50', + ghost: 'bg-transparent text-gray-800 hover:bg-gray-100', + }; + + const sizeClasses = { + xs: 'h-7 px-3 text-xs', + sm: 'h-8 px-4 text-sm', + md: 'h-10 px-5 text-base', + lg: 'h-12 px-6 text-lg', + }; + + return ( + + ); +}; +``` + +## 2.3 创建容器与布局组件 + +### 任务列表 +- [ ] 创建 Layout 组件(`src/components/containers/Layout.tsx`) +- [ ] 创建 Sidebar 组件(`src/components/containers/Sidebar.tsx`) +- [ ] 创建 Header 组件(`src/components/containers/Header.tsx`) + +**参考文档**:`spec/ui-design-components.md` 第 3 节 + +## 2.4 创建业务组件 + +### 任务列表 +- [ ] 创建 MessageBubble 组件(`src/components/features/MessageBubble.tsx`) +- [ ] 创建 SuggestionCard 组件(`src/components/features/SuggestionCard.tsx`) +- [ ] 创建 StatCard 组件(`src/components/features/StatCard.tsx`) +- [ ] 创建 ConversationCard 组件(`src/components/features/ConversationCard.tsx`) + +**参考文档**:`spec/ui-design-components.md` 第 4 节 + +## 2.5 创建状态管理 + +### 文件:`src/store/ui.ts`(Zustand store) + +```typescript +import create from 'zustand'; + +interface UIState { + sidebarOpen: boolean; + toggleSidebar: () => void; + + selectedConversation: string | null; + setSelectedConversation: (id: string | null) => void; + + theme: 'light' | 'dark'; + setTheme: (theme: 'light' | 'dark') => void; +} + +export const useUIStore = create((set) => ({ + sidebarOpen: true, + toggleSidebar: () => set(state => ({ sidebarOpen: !state.sidebarOpen })), + + selectedConversation: null, + setSelectedConversation: (id) => set({ selectedConversation: id }), + + theme: 'dark', + setTheme: (theme) => set({ theme }), +})); +``` + +## 2.6 参考文档 + +| 文档 | 用途 | +|------|------| +| **spec/ui-design-components.md** | 所有组件详细规范 | +| **spec/UI_DESIGN_INDEX.md** | 快速导航组件库 | + +--- + +# 第三阶段:核心功能开发(第 2-3 周) + +## 3.1 音频采集与转录 + +### 第一步:安装音频库 + +```bash +npm install \ + node-record-lpcm16 \ + web-audio-api \ + @types/node +``` + +### 第二步:创建音频服务 + +**文件**:`src/main/services/AudioService.ts` + +参考文档:`spec/audio-capture-tech-note.md` + +```typescript +// 伪代码框架 +class AudioService { + private micStream: any; + private systemAudioStream: any; + + async startCapture() { + // 1. 请求麦克风权限 + // 2. 启动麦克风采集 + // 3. 启动系统音频捕获 + } + + async stopCapture() { + // 停止所有采集 + } + + async getMicAudio(): Promise { + // 获取麦克风音频缓冲区 + } + + async getSystemAudio(): Promise { + // 获取系统音频缓冲区 + } + + async mergeAudio(mic: Buffer, system: Buffer): Promise { + // 混合两路音频 + } +} + +export const audioService = new AudioService(); +``` + +### 第三步:IPC 通信桥接 + +**文件**:`src/main/ipc/audio.ts` + +```typescript +import { ipcMain } from 'electron'; +import { audioService } from '../services/AudioService'; + +export function setupAudioIPC() { + ipcMain.handle('audio:start-capture', async () => { + await audioService.startCapture(); + }); + + ipcMain.handle('audio:stop-capture', async () => { + await audioService.stopCapture(); + }); + + ipcMain.handle('audio:get-mic', async () => { + return audioService.getMicAudio(); + }); +} +``` + +## 3.2 LLM 集成与 API 调用 + +### 第一步:创建 LLM 配置类型 + +**文件**:`src/types/llm.ts` + +```typescript +export interface LLMProvider { + id: string; + name: string; + modelId: string; + apiKey: string; + apiUrl?: string; + isActive: boolean; +} + +export interface LLMResponse { + content: string; + tokens: number; + model: string; +} + +export interface SuggestionRequest { + context: string; + conversationHistory: Message[]; + userMessage: string; +} + +export interface Suggestion { + text: string; + tags: string[]; + expectedImpact: number; +} +``` + +### 第二步:创建 LLM 服务 + +**文件**:`src/main/services/LLMService.ts` + +参考文档:`spec/llm-integration.md` + +```typescript +class LLMService { + private provider: LLMProvider; + + setProvider(provider: LLMProvider) { + this.provider = provider; + } + + async testConnection(): Promise { + // 发送测试请求到 LLM API + // 返回连接成功/失败 + } + + async generateSuggestions(request: SuggestionRequest): Promise { + // 1. 构建提示词(Prompt Engineering) + // 2. 调用 LLM API + // 3. 解析响应,提取建议 + // 4. 返回建议列表 + } + + async analyzeConversation(messages: Message[]): Promise { + // 1. 构建分析提示词 + // 2. 调用 LLM API + // 3. 返回分析结果 + } +} + +export const llmService = new LLMService(); +``` + +## 3.3 数据模型与存储 + +### 第一步:安装 SQLite 库 + +```bash +npm install better-sqlite3 @types/better-sqlite3 +``` + +### 第二步:创建数据库初始化脚本 + +**文件**:`src/main/db/init.ts` + +参考文档:`spec/data-model.md` + +```typescript +import Database from 'better-sqlite3'; +import path from 'path'; + +const dbPath = path.join(process.env.APPDATA || process.env.HOME, 'LiveGalGame', 'app.db'); + +export const db = new Database(dbPath); + +export function initializeDatabase() { + // 创建表 + db.exec(` + CREATE TABLE IF NOT EXISTS person ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + nickname TEXT, + personality_desc TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS conversation ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + person_id TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (person_id) REFERENCES person(id) + ); + + CREATE TABLE IF NOT EXISTS turn ( + id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL, + role TEXT NOT NULL, + content TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + is_key_point BOOLEAN DEFAULT FALSE, + FOREIGN KEY (conversation_id) REFERENCES conversation(id) + ); + + CREATE TABLE IF NOT EXISTS score ( + id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL, + previous_score REAL, + current_score REAL, + delta REAL, + turn_id TEXT, + FOREIGN KEY (conversation_id) REFERENCES conversation(id), + FOREIGN KEY (turn_id) REFERENCES turn(id) + ); + `); +} +``` + +## 3.4 参考文档 + +| 文档 | 用途 | +|------|------| +| **spec/audio-capture-tech-note.md** | 音频采集实现细节 | +| **spec/llm-integration.md** | LLM 集成与提示工程 | +| **spec/data-model.md** | SQLite 表设计 | + +--- + +# 第四阶段:页面与业务逻辑集成(第 3-4 周) + +## 4.1 Electron 主窗口与 HUD 浮窗 + +### 第一步:创建主进程入口 + +**文件**:`src/main/index.ts` + +```typescript +import { app, BrowserWindow, ipcMain } from 'electron'; +import path from 'path'; +import { isDev } from './utils'; +import { setupAudioIPC } from './ipc/audio'; +import { setupLLMIPC } from './ipc/llm'; +import { initializeDatabase } from './db/init'; + +let mainWindow: BrowserWindow; +let chatWindow: BrowserWindow; + +async function createMainWindow() { + mainWindow = new BrowserWindow({ + width: 1400, + height: 900, + minWidth: 1200, + minHeight: 800, + webPreferences: { + preload: path.join(__dirname, 'preload.ts'), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + const startUrl = isDev + ? 'http://localhost:8080' + : `file://${path.join(__dirname, '../../dist/index.html')}`; + + mainWindow.loadURL(startUrl); + + if (isDev) { + mainWindow.webContents.openDevTools(); + } +} + +async function createChatWindow() { + chatWindow = new BrowserWindow({ + width: 440, + height: 700, + minWidth: 360, + minHeight: 480, + alwaysOnTop: true, + transparent: true, + frame: false, + webPreferences: { + preload: path.join(__dirname, 'preload.ts'), + contextIsolation: true, + }, + }); + + const startUrl = isDev + ? 'http://localhost:8080/chat' + : `file://${path.join(__dirname, '../../dist/index.html')}`; + + chatWindow.loadURL(startUrl); +} + +app.on('ready', async () => { + initializeDatabase(); + setupAudioIPC(); + setupLLMIPC(); + + await createMainWindow(); + await createChatWindow(); +}); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); +``` + +### 第二步:创建 React 路由 + +**文件**:`src/renderer/App.tsx` + +```typescript +import React from 'react'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { Layout } from '@components/containers/Layout'; +import Dashboard from '@pages/Dashboard'; +import LLMConfig from '@pages/LLMConfig'; +import ConversationDetail from '@pages/ConversationDetail'; +import ChatWindow from '@pages/ChatWindow'; + +export function App() { + return ( + + + {/* 主窗口路由 */} + } /> + } /> + } /> + + {/* HUD 浮窗路由 */} + } /> + + + ); +} +``` + +## 4.2 页面开发顺序 + +### 优先级 P0(第一周完成) + +1. **Dashboard(主页)** - `src/pages/Dashboard.tsx` + - 参考:`spec/ui-design-03-dashboard.md` + - 包含:欢迎区、统计卡片、对话列表、新建对话 + - 预计时间:2-3 天 + - 依赖组件:StatCard, ConversationCard, Header + +2. **LLMConfig(配置页)** - `src/pages/LLMConfig.tsx` + - 参考:`spec/ui-design-02-llm-config.md` + - 包含:模型卡片、添加模型、连接测试 + - 预计时间:2-3 天 + - 依赖组件:Card, Button, Input, Modal + +3. **ChatWindow(HUD 浮窗)** - `src/pages/ChatWindow.tsx` + - 参考:`spec/ui-design-01-chat-window.md` + - 包含:对话气泡、AI 建议、状态指示、操作栏 + - 预计时间:3-4 天 + - 依赖组件:MessageBubble, SuggestionCard, Button, Spinner + +### 优先级 P1(第二周完成) + +4. **ConversationDetail(对话详情)** - `src/pages/ConversationDetail.tsx` + - 参考:`spec/ui-design-04-conversation-detail.md` + - 包含:对话内容、AI 分析、好感度曲线、消息编辑 + - 预计时间:4-5 天 + - 依赖组件:MessageBubble, Card, Button, Modal, Chart + +## 4.3 业务逻辑集成 + +### 创建自定义 Hooks + +**文件**:`src/hooks/useConversation.ts` + +```typescript +import { useQuery, useMutation } from '@react-query/core'; +import { conversationService } from '@utils/api'; + +export function useConversation(id: string) { + return useQuery(['conversation', id], () => + conversationService.getConversation(id) + ); +} + +export function useCreateConversation() { + return useMutation((data) => + conversationService.createConversation(data) + ); +} + +export function useSaveMessage() { + return useMutation(({ conversationId, message }) => + conversationService.addMessage(conversationId, message) + ); +} +``` + +## 4.4 参考文档 + +| 文档 | 用途 | 优先级 | +|------|------|-------| +| **spec/ui-design-03-dashboard.md** | Dashboard 页面规范 | P0 | +| **spec/ui-design-02-llm-config.md** | LLM 配置页规范 | P0 | +| **spec/ui-design-01-chat-window.md** | Chat HUD 规范 | P0 | +| **spec/ui-design-04-conversation-detail.md** | 对话详情页规范 | P1 | +| **spec/hud-ux.md** | HUD 交互细节 | P0 | + +--- + +# 第五阶段:打包、测试与优化(第 4-5 周) + +## 5.1 单元测试与集成测试 + +### 第一步:编写测试用例 + +**文件**:`tests/unit/Button.test.tsx` + +```typescript +import { render, screen, fireEvent } from '@testing-library/react'; +import { Button } from '@components/base/Button'; + +describe('Button Component', () => { + it('renders button with correct text', () => { + render(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('calls onClick handler when clicked', () => { + const handleClick = jest.fn(); + render(); + + fireEvent.click(screen.getByText('Click')); + expect(handleClick).toHaveBeenCalled(); + }); + + it('applies primary variant styles', () => { + render(); + const button = screen.getByText('Submit'); + expect(button).toHaveClass('bg-brand-primary'); + }); +}); +``` + +### 第二步:运行测试 + +```bash +npm test # 运行一次 +npm run test:watch # 监听模式 +``` + +## 5.2 性能优化 + +### 参考清单 + +- [ ] 组件使用 React.memo 避免不必要重渲染 +- [ ] 使用 useCallback 缓存函数 +- [ ] 列表使用虚拟化(react-window)处理大数据量 +- [ ] 图片优化(使用 WebP 格式) +- [ ] 代码分割(code splitting) + +参考文档:`spec/ui-design-components.md` 第 10 节 + +## 5.3 构建和打包 + +### 第一步:编译应用 + +```bash +npm run build +``` + +### 第二步:生成应用程序包 + +```bash +# Windows +npm run package:win + +# macOS +npm run package:mac + +# 两个平台 +npm run package +``` + +参考文档:`spec/build-and-release.md` + +## 5.4 参考文档 + +| 文档 | 用途 | +|------|------| +| **spec/test-plan.md** | 完整测试计划 | +| **spec/build-and-release.md** | 打包与发版流程 | + +--- + +# 第六阶段:发版与迭代(第 5-6 周) + +## 6.1 版本发布 + +### 第一步:更新版本号 + +```bash +# package.json +{ + "version": "1.0.0" +} +``` + +### 第二步:生成 CHANGELOG + +``` +## v1.0.0 (2025-12-XX) + +### 新增 +- ✅ 对话 HUD 浮窗实现 +- ✅ AI 建议生成 +- ✅ LLM 模型配置 +- ✅ 对话数据存储与分析 + +### 修复 +- 修复音频采集延迟问题 +- 修复 macOS 透明窗口适配 + +### 性能 +- 优化消息列表虚拟化 +- 减少内存占用 30% +``` + +### 第三步:发布应用 + +```bash +npm run release +``` + +参考文档:`spec/build-and-release.md` + +## 6.2 用户反馈与迭代 + +- 收集用户反馈(GitHub Issues、用户调查等) +- 优先级排序(Critical > High > Medium > Low) +- 规划下一版本(v1.1) + +## 6.3 持续集成/部署 + +参考文档:`.github/workflows/release.yml` + +--- + +# 开发检查清单 + +## ✅ 第一阶段完成标志 + +- [ ] 所有依赖安装完毕 +- [ ] 目录结构创建完整 +- [ ] TypeScript 配置正确 +- [ ] `npm run build` 成功编译 +- [ ] Webpack dev server 正常启动 + +## ✅ 第二阶段完成标志 + +- [ ] 所有基础组件完成(6 个) +- [ ] 容器组件完成(3 个) +- [ ] 业务组件完成(4 个) +- [ ] 设计系统配置(令牌、样式、动画) +- [ ] Storybook 可视化展示所有组件 +- [ ] 组件单元测试覆盖率 > 80% + +## ✅ 第三阶段完成标志 + +- [ ] 音频采集功能正常工作 +- [ ] LLM API 连接测试成功 +- [ ] 数据库初始化完成 +- [ ] IPC 通信桥接建立 +- [ ] 本地存储正常读写 + +## ✅ 第四阶段完成标志 + +- [ ] Dashboard 页面完整实现 +- [ ] LLM Config 页面完整实现 +- [ ] Chat HUD 浮窗完整实现 +- [ ] Conversation Detail 页面完整实现 +- [ ] 页面间导航正常工作 +- [ ] 功能测试通过(对照 spec/test-plan.md) + +## ✅ 第五阶段完成标志 + +- [ ] 单元测试覆盖率 > 80% +- [ ] 集成测试通过 +- [ ] 性能指标达标(首屏 < 2s,帧率 60fps) +- [ ] Windows 和 macOS 打包成功 +- [ ] 应用程序能正常安装和运行 + +## ✅ 第六阶段完成标志 + +- [ ] v1.0.0 发版成功 +- [ ] 用户反馈收集与整理完成 +- [ ] v1.1 迭代计划制定完成 + +--- + +# 快速命令参考 + +```bash +# 开发 +npm run dev # 启动开发服务器 + Electron +npm run webpack:dev # 仅启动 Webpack dev server +npm start # 仅启动 Electron + +# 构建 +npm run build # 编译 React + TypeScript +npm run package # 打包应用程序(Win + Mac) +npm run package:win # 仅打包 Windows +npm run package:mac # 仅打包 macOS + +# 测试 +npm test # 运行测试 +npm run test:watch # 测试监听模式 + +# 文档 +npm run storybook # 启动 Storybook(http://localhost:6006) + +# 代码质量 +npm run lint # 检查代码风格 +``` + +--- + +# 常见问题(FAQ) + +## Q1:如何在 macOS 上处理代码签名? + +A:参考 `spec/build-and-release.md` 第 3.2 节(macOS 硬化与公证) + +## Q2:如何添加代理以加速下载? + +A:根据用户规则,使用 `dl1` 命令启动代理,并配置 npm: +```bash +dl1 # 启动代理 +npm config set registry http://registry.proxy.local +``` + +## Q3:音频采集在 Linux 上支持吗? + +A:当前设计仅支持 Windows 和 macOS,Linux 支持需要后续扩展 + +## Q4:如何本地测试 LLM 集成? + +A:在 `spec/llm-integration.md` 中找到本地测试脚本 + +## Q5:如何生成热更新? + +A:参考 `spec/build-and-release.md` 第 3.4 节(自动更新配置) + +--- + +# 进阶主题 + +## 性能监控 + +参考 `spec/ui-design-components.md` 第 10 节性能优化部分 + +## 无障碍支持 + +参考 `spec/ui-design-components.md` 第 11 节无障碍指南 + +## 国际化(i18n) + +当前版本仅支持中文,国际化支持需要后续规划 + +--- + +# 支持与联系 + +- **技术支持**:team@livegalgame.local +- **Bug 报告**:GitHub Issues +- **功能建议**:Discussions +- **设计反馈**:Figma 评论 + +--- + +**文档版本**:v1.0 +**最后更新**:2025-11-12 +**预计完成时间**:4-6 周(取决于团队规模) + diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 0000000..9364fe3 --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,39 @@ +LiveGalGame Desktop - 开发总览 + +概述 +LiveGalGame Desktop 是基于 Electron 的跨平台桌面应用(Windows/macOS)。它将移动端原型的“准备 → 实时辅助 → 复盘学习”核心用户旅程重构为桌面体验:与任意线上聊天工具(微信、QQ、Telegram、Discord、Zoom/Teams 等)并行工作,通过浮动 HUD 提供实时转录、AI 建议与即时反馈动画,并在主窗体中完成对话归档与复盘分析。 + +关键差异(桌面版相对移动端) +- 无摄像头相关能力与权限请求;仅麦克风与系统音频捕获。 +- 面向“线上聊天”的并行辅助:以半透明浮窗(HUD)叠加在任意聊天应用之上,不干扰当前窗口焦点。 +- 系统音频采集:Windows 通过 WASAPI Loopback(Electron desktopCapturer);macOS 通过屏幕共享音轨或虚拟声卡方案(详见 spec/audio-capture-tech-note.md)。 +- 权限与安全模型按桌面系统规范(麦克风、屏幕录制/系统音频、辅助功能/可选的快捷键监听)。 + +仓库结构(文档) +- spec/prd-desktop.md 桌面版 PRD(以核心用户旅程为主线) +- spec/tech-architecture.md 技术架构与模块边界 +- spec/audio-capture-tech-note.md 系统音频与麦克风采集技术方案 +- spec/llm-integration.md LLM/语音模型集成与配置体验 +- spec/hud-ux.md HUD 浮窗交互与状态机 +- spec/data-model.md 数据模型与存储 +- spec/build-and-release.md 构建、签名与发布(Win/mac) +- spec/privacy-and-permissions.md 隐私、权限与安全 +- spec/test-plan.md 测试计划与验收标准 + +本地开发(概览) +1) Node.js 20+,pnpm 8+(建议) +2) 克隆仓库并安装依赖:`pnpm install` +3) 开发启动:`pnpm dev`(主进程 + 渲染器热重载) +4) 打包:`pnpm build:win` / `pnpm build:mac`(详见 spec/build-and-release.md) + +网络与下载加速(可选) +- 若需要下载外部依赖(如语音/ASR 模型或静态资源),可先执行本地代理命令 `dl1` 来启用代理以加速;大文件下载建议采用多进程/分片并发方式(实现细节在后续实现阶段落地)。 + +最低系统要求 +- Windows 10 19045+(x64/arm64 可选)、macOS 12+(Intel/Apple Silicon) +- 麦克风可用;若需捕获系统音频:Windows 无需额外驱动,macOS 需屏幕录制权限或使用虚拟声卡 + +版权与许可 +根据企业内部策略补充。默认保留所有权利。 + + diff --git a/desktop/spec/UI_DESIGN_INDEX.md b/desktop/spec/UI_DESIGN_INDEX.md new file mode 100644 index 0000000..94c26ce --- /dev/null +++ b/desktop/spec/UI_DESIGN_INDEX.md @@ -0,0 +1,300 @@ +# 桌面端 UI 设计文档索引 + +> **项目**:LiveGalGame 桌面版 +> **平台**:Electron(Windows + macOS) +> **设计系统**:完整的倒推提示词(Reverse Prompt),用于指导 LLM 精确开发界面 + +--- + +## 📋 文档结构 + +### 核心 UI 规范文档 + +| 文档名 | 覆盖页面 | 优先级 | 大小 | +|-------|---------|-------|------| +| **ui-design-01-chat-window.md** | 对话 HUD 浮窗 | P0 | ~15KB | +| **ui-design-02-llm-config.md** | AI 模型配置管理 | P1 | ~18KB | +| **ui-design-03-dashboard.md** | 主页/仪表板 | P1 | ~16KB | +| **ui-design-04-conversation-detail.md** | 对话详情与 AI 分析 | P1 | ~20KB | +| **ui-design-components.md** | 可复用组件库 | P1 | ~25KB | + +### 相关技术文档 + +| 文档名 | 功能 | 链接 | +|-------|------|------| +| **prd-desktop.md** | 产品需求 | 核心用户旅程 | +| **tech-architecture.md** | 系统架构 | Electron 分层设计 | +| **audio-capture-tech-note.md** | 音频采集 | 麦克风 + 系统音频 | +| **llm-integration.md** | LLM 集成 | 模型接口与提示工程 | +| **hud-ux.md** | HUD 交互 | 浮窗交互细节 | +| **data-model.md** | 数据模型 | SQLite 表设计 | +| **build-and-release.md** | 构建发布 | Win/Mac 打包流程 | + +--- + +## 🎯 快速导航 + +### 场景 1:我要开发聊天 HUD 浮窗 + +**快速路径**: +1. 📖 阅读 `ui-design-01-chat-window.md`(完整的 UI 规范) +2. 📖 参考 `ui-design-components.md` 中的 MessageBubble、SuggestionCard 组件 +3. 🛠️ 使用提供的 Tailwind CSS 颜色变量和 Framer Motion 动画预设 +4. ✅ 对照第 12 节验收标准进行自测 + +**关键代码片段位置**: +- 消息气泡:第 3 节 +- 推荐卡片:第 4 节 +- 状态指示器:第 5 节 +- 底部操作栏:第 6 节 + +--- + +### 场景 2:我要开发 LLM 配置页面 + +**快速路径**: +1. 📖 阅读 `ui-design-02-llm-config.md` +2. 📖 参考 `ui-design-components.md` 中的 Card、Button、Input 组件 +3. 🛠️ 模态框使用 Framer Motion scaleIn 预设 +4. ✅ 对照第 12 节验收标准 + +**关键区域**: +- 侧边栏导航:第 2 节 +- 模型卡片列表:第 4 节 +- 添加模型对话框:第 5 节 + +--- + +### 场景 3:我要开发主页/仪表板 + +**快速路径**: +1. 📖 阅读 `ui-design-03-dashboard.md` +2. 📖 参考 `ui-design-components.md` 中的 StatCard、ConversationCard 组件 +3. 🛠️ 对话卡片使用 container + item stagger 动画 +4. ✅ 对照第 15 节验收标准 + +**关键区域**: +- 统计卡片网格:第 4 节 +- 对话卡片列表:第 6 节 +- 新建对话模态框:第 8 节 + +--- + +### 场景 4:我要开发对话详情与分析页面 + +**快速路径**: +1. 📖 阅读 `ui-design-04-conversation-detail.md` +2. 📖 参考消息气泡(重用 01 文档)和分析卡片规范 +3. 🛠️ 右侧分析板使用 slideInRight 动画 +4. ✅ 对照第 15 节验收标准 + +**关键区域**: +- 对话内容区:第 4 节 +- 右侧分析板:第 6 节 +- 消息编辑对话框:第 8 节 + +--- + +### 场景 5:我要构建组件库并实现代码复用 + +**快速路径**: +1. 📖 阅读 `ui-design-components.md` 全文 +2. 🛠️ 按照第 2-4 节创建基础组件、容器组件、业务组件 +3. 📝 在 Storybook 中编写每个组件的 Story(第 12 节示例) +4. ✅ 编写 Jest 测试用例(第 9 节) + +**目录结构**: +``` +src/components/ +├── base/ +│ ├── Button.tsx +│ ├── Input.tsx +│ ├── Card.tsx +│ ├── Badge.tsx +│ └── Tag.tsx +├── containers/ +│ ├── Layout.tsx +│ ├── Sidebar.tsx +│ └── Header.tsx +├── features/ +│ ├── MessageBubble.tsx +│ ├── SuggestionCard.tsx +│ ├── StatCard.tsx +│ └── ConversationCard.tsx +├── hooks/ +│ ├── useTheme.ts +│ └── useUIStore.ts +└── lib/ + ├── animations.ts + └── tokens.ts +``` + +--- + +## 🎨 设计令牌快速参考 + +### 颜色 +``` +品牌粉红:#D91B5C +深粉红:#C2185B +浅粉红:rgba(217, 27, 92, 0.1) + +中性灰:#6B7280 ~ #1F2937 +浅灰:#E5E7EB ~ #F9FAFB + +状态绿:#10B981 +状态红:#EF4444 +状态黄:#F59E0B +状态蓝:#3B82F6 +``` + +### 间距系统 +``` +4px (xs) → 8px (sm) → 16px (md) → 24px (lg) → 32px (xl) → 40px (2xl) +``` + +### 圆角 +``` +4px (xs) → 6px (sm) → 8px (md) → 12px (lg) → 16px (xl) +``` + +### 阴影 +``` +xs: 0 1px 2px rgba(0, 0, 0, 0.05) +sm: 0 1px 3px rgba(0, 0, 0, 0.1) +md: 0 4px 6px rgba(0, 0, 0, 0.1) +lg: 0 10px 15px rgba(0, 0, 0, 0.15) +xl: 0 10px 40px rgba(0, 0, 0, 0.2) +``` + +--- + +## 🚀 开发工作流 + +### 第一步:环境搭建 +```bash +# 安装依赖 +npm install react react-dom framer-motion zustand react-query + +# Electron 相关 +npm install electron electron-builder + +# UI 工具 +npm install @tailwindcss/forms autoprefixer postcss tailwindcss + +# 开发工具 +npm install -D @testing-library/react @testing-library/jest-dom jest storybook +``` + +### 第二步:创建 Tailwind 配置 +参考 `ui-design-components.md` 第 1.1 节的设计令牌,配置 `tailwind.config.js` + +### 第三步:创建组件 +按优先级: +1. 基础组件(Button, Input, Card, Badge) +2. 容器组件(Layout, Sidebar, Header) +3. 业务组件(MessageBubble, SuggestionCard 等) + +### 第四步:集成到页面 +- 先做 Chat Window(P0) +- 再做 Dashboard、LLM Config(P1) +- 最后做 Conversation Detail(P1 低优先级) + +### 第五步:测试与优化 +- 功能测试(对照验收标准) +- 性能测试(60fps 帧率) +- 跨平台测试(Win + macOS) + +--- + +## 📐 页面尺寸与响应式 + +### 桌面版断点 +- **≥ 1400px**:完整三列或两列布局 +- **1000px ~ 1399px**:压缩模式 +- **< 1000px**:单列或模态弹出(用于小屏模式) + +### 窗口最小尺寸 +- **主窗口**:1200px × 800px +- **Chat HUD**:440px × 600px~800px +- **对话框**:400px~800px + +--- + +## 🎬 动画与交互预设 + +### 常用动画(Framer Motion) +- **fadeInOut**:淡入淡出(200ms) +- **slideUp**:从下滑入(300ms) +- **slideInRight**:从右滑入(300ms) +- **scaleIn**:缩放 + 淡入(200ms) +- **container + item**:Stagger 列表(50ms 间隔) + +### 按钮交互 +- Hover:背景色变化 + 指针变手指 +- Active:缩放 0.95 + 快速恢复 +- Disabled:opacity 0.5 + cursor not-allowed +- Loading:旋转动画 + 文字变为"加载中..." + +--- + +## ✅ 验收清单 + +### 开发完成时,需检查: +- [ ] 所有按钮、输入框、卡片样式与设计文档一致 +- [ ] 动画流畅(60fps,无卡顿) +- [ ] 文字对比度符合 WCAG AA 标准(≥ 4.5:1) +- [ ] 键盘快捷键正常工作 +- [ ] 屏幕阅读器友好(ARIA 标签完整) +- [ ] Windows 和 macOS 显示一致 +- [ ] 响应式布局在不同尺寸正常工作 + +--- + +## 📖 设计文档维护 + +### 文档修改流程 +1. 在 Figma 中修改设计 +2. 更新对应的 MD 文档(含截图或伪代码) +3. 更新 CHANGELOG(版本号 + 变更说明) +4. 通知前端团队进行代码同步 + +### 文档版本 +- **v1.0**:初版(2025-11-12) +- 未来版本:需要时更新 + +--- + +## 🔗 相关资源链接 + +### 在线工具 +- Figma 设计文件:(待提供链接) +- Storybook 在线预览:http://localhost:6006 +- Tailwind Color Palette:https://tailwindcss.com/docs/customizing-colors + +### 参考文档 +- Tailwind CSS 文档:https://tailwindcss.com/docs +- Framer Motion 文档:https://www.framer.com/motion/ +- Electron 文档:https://www.electronjs.org/docs +- React 最佳实践:https://react.dev/ + +### 外部组件库(可选集成) +- **UI 框架**:Shadcn UI、Ant Design、Material-UI +- **图表库**:Recharts、Chart.js、ECharts +- **表格**:TanStack Table(React Table) +- **表单**:React Hook Form、Formik + +--- + +## 👥 联系方式 + +- **设计**:[产品设计团队] +- **前端**:[前端工程团队] +- **反馈**:在 GitHub Issues 或团队 Slack 频道提出 + +--- + +**文档最后更新**:2025-11-12 +**维护者**:产品与技术团队 +**许可证**:Internal Use Only + diff --git a/desktop/spec/audio-capture-tech-note.md b/desktop/spec/audio-capture-tech-note.md new file mode 100644 index 0000000..2bfdc4f --- /dev/null +++ b/desktop/spec/audio-capture-tech-note.md @@ -0,0 +1,54 @@ +音频捕获技术方案(麦克风 + 系统音频) + +目标 +- 同时捕获用户麦克风与对方(应用)系统音频,进行双通道转写与上下文分析。 +- 在 Windows 与 macOS 上以 Electron 提供一致体验。 + +方案总览 +1) 麦克风:标准 getUserMedia({ audio: true }),设备可选;回声消除/降噪开启。 +2) 系统音频: + - Windows:Electron desktopCapturer 选择 Entire Screen + audio:true(底层走 WASAPI Loopback)。 + - macOS:desktopCapturer 捕获屏幕并勾选系统音频(需屏幕录制权限);若某些版本无系统音轨,则引导安装虚拟声卡(如 BlackHole)并将其设为输出-输入桥。 +3) 合并/分发:将 micStream 与 sysStream 分发到各自 ASR 管线;必要时做对齐与去混叠。 + +权限与提示 +- Windows:首次无需显式系统音频权限;仅麦克风权限提示。 +- macOS:需要“屏幕录制”权限以捕获系统音频轨,以及“麦克风”权限;需文案解释用途。 + +时序与数据流 +- 设备枚举 → 选择设备 → 启动 micStream 与 sysStream → VAD/分段 → ASR(本地或远端)→ 渲染器显示转写。 +- 采样率统一至 16k/24k(与 ASR 兼容),采用 WebAudio 或 AudioWorklet 做重采样。 + +示例(伪代码) + +```ts +// 获取麦克风 +const mic = await navigator.mediaDevices.getUserMedia({ + audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } +}); + +// 获取系统音频(带屏幕) +const sources = await desktopCapturer.getSources({ types: ['screen'], fetchWindowIcons: false }); +const sys = await navigator.mediaDevices.getUserMedia({ + audio: { mandatory: { chromeMediaSource: 'desktop' } }, + video: { + mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: sources[0].id } + } +}); + +// 仅使用音轨 +const sysAudio = new MediaStream(sys.getAudioTracks()); + +// 分发到 ASR +asr.consume('mic', mic); +asr.consume('sys', sysAudio); +``` + +性能与稳定性 +- 将捕获与编码/重采样放在 AudioWorklet 或 Worker,避免阻塞 UI。 +- 控制内存与延迟:每段 2-5 秒;启用背压丢弃过期片段。 + +降级策略 +- macOS 若无法获取系统音轨:引导用户选择虚拟声卡输出;或只启用麦克风通道(提示功能受限)。 + + diff --git a/desktop/spec/build-and-release.md b/desktop/spec/build-and-release.md new file mode 100644 index 0000000..f0a6ad1 --- /dev/null +++ b/desktop/spec/build-and-release.md @@ -0,0 +1,51 @@ +构建、签名与发布(Windows/macOS) + +工具链 +- electron-builder 24+(建议)或 electron-forge。本文以 electron-builder 为例。 +- Node.js 20+,pnpm。 + +目录与脚本(建议) +- package.json + - "dev": 同时启动 main 与 renderer,启用热重载 + - "build:win": electron-builder --win nsis + - "build:mac": electron-builder --mac dmg + +electron-builder 配置要点 +- appId: com.livegalgame.desktop +- asar: true +- files: dist/**, build/** +- extraResources: 语音/模型/词典(如有) +- mac: + - category: public.app-category.productivity + - hardenedRuntime: true + - entitlements: build/mac/entitlements.plist + - entitlementsInherit: build/mac/entitlements.plist + - gatekeeperAssess: false + - notarize: 使用 Apple ID/Keychain(CI 中配置 APPLE_ID 等) +- win: + - target: nsis + - publisherName: 组织/公司名称 + - signingHashAlgorithms: ["sha256"] + - rfc3161TimeStampServer: http://timestamp.digicert.com + +权限与 Entitlements(macOS) +- 需要:麦克风(com.apple.security.device.audio-input) +- 屏幕录制:不在 Entitlements 配置,而在系统“隐私与安全性”里由应用触发时请求 +- 可选:辅助功能(用于全局快捷键/前台检测,通常不必) + +CI/CD(建议) +- GitHub Actions / GitLab CI: + - 矩阵构建:macos-latest + windows-latest + - 缓存 node_modules;签名证书和 Apple Notarization 凭据用 Secrets + - 工件:.dmg、.exe(或 .msi)、RELEASES +- 自动更新:electron-updater + 静态文件托管(GitHub Releases/自建 CDN) + +验证清单(出厂前) +- 安装成功;首次启动权限请求文案正确;音频测试可用。 +- HUD 能置顶且不抢焦点;Win/mac 均可捕获系统音频(或提供降级指引)。 +- 签名有效;macOS 可通过 Gatekeeper;可完成 Notarization。 + +网络与加速 +- 下载外部资源(ASR/LLM 相关权重/词典)时,支持代理与断点续传。可建议用户运行 `dl1` 以加速,并使用多进程/分片并发。 + + diff --git a/desktop/spec/data-model.md b/desktop/spec/data-model.md new file mode 100644 index 0000000..4dd800f --- /dev/null +++ b/desktop/spec/data-model.md @@ -0,0 +1,58 @@ +数据模型与存储(SQLite) + +存储引擎 +- 本地 SQLite(better-sqlite3);加密字段(API Key)存 OS Keychain/DPAPI。 +- 迁移机制:按版本号执行 DDL;备份与导入/导出 JSON。 + +表设计(简版) +- person + - id (pk, uuid) + - name (text) + - relation_tag (text) // 暧昧对象/同事/好友等 + - notes (text) // 备注,供提示工程 + - favor_score (int) // 当前好感度 0-100 + - created_at, updated_at +- conversation + - id (pk) + - person_id (fk) + - started_at, ended_at + - meta (json) // 设备、模型、环境信息 +- turn + - id (pk) + - conversation_id (fk) + - role (text) // user/peer + - text (text) // 转写结果 + - start_ms, end_ms + - source (text) // mic/system +- event + - id (pk) + - conversation_id (fk) + - type (text) // suggestion_shown/accepted/feedback + - payload (json) // 建议卡内容、被采纳与否、打分等 + - ts +- score + - id (pk) + - conversation_id (fk) + - delta (int) // +20/-5 + - reason (text) + - ts +- model_profile + - id (pk) + - provider (text) // openai/anthropic/local + - model (text) + - endpoint (text) + - is_default (bool) +- settings + - id (pk, 1) + - audio_prefs (json) // 设备、采样率、VAD + - hud_prefs (json) + - privacy_prefs (json) + +查询与索引 +- turn(conversation_id, start_ms) 索引以便时间序列读取。 +- event(conversation_id, ts) 与 score(conversation_id, ts) 索引。 + +导出与备份 +- 导出单次对话(JSON + 可选音频片段);可批量导出人物档案与曲线。 + + diff --git a/desktop/spec/hud-ux.md b/desktop/spec/hud-ux.md new file mode 100644 index 0000000..6f6d140 --- /dev/null +++ b/desktop/spec/hud-ux.md @@ -0,0 +1,33 @@ +HUD 浮窗交互与状态机 + +目标 +- 在不打断用户使用任意聊天应用的情况下,提供实时转写、建议卡与即时反馈动画。 + +布局 +- 位置:屏幕右下角默认;可拖拽并吸附边缘;可记忆上次位置。 +- 结构: + - 顶部:状态条(采集指示、连接状态、静音按钮、展开/收起、设置入口)。 + - 左列:对方转写(系统音频)。 + - 右列:我的转写(麦克风)。 + - 底部:建议卡区域(2-3 张)与“一键复制/插入”按钮。 + - 反馈:好感度进度条 + 流畅的“+N/-N”动画。 + +交互要点 +- 不抢焦点:使用 AlwaysOnTop + 可选 Mouse Through;鼠标靠近即解锁交互。 +- 快捷键:显示/隐藏 HUD,开始/停止监听,复制建议卡(可自定义)。 +- 体积自适应:当无转写/无建议时自动收起为 64px 小条;有活动时展开。 + +状态机(简) +- idle → preparingDevices → listening → suggesting → feedback → listening + - preparingDevices:设备枚举与权限确认;失败进入 error 子状态并给出修复指南。 + - suggesting:接收 LLM 流式返回,逐步渲染卡片。 + - feedback:评分动画 1-2s,不阻塞继续监听。 + +无障碍与国际化 +- 字体放大、对比度增强;中英文切换;屏幕阅读器标签。 + +异常与边界 +- 设备被占用/拔出:顶部状态条显红点并可一键重试。 +- CPU/延迟过高:自动降低分段长度或暂停系统音频,仅保留麦克风。 + + diff --git a/desktop/spec/llm-integration.md b/desktop/spec/llm-integration.md new file mode 100644 index 0000000..50118f3 --- /dev/null +++ b/desktop/spec/llm-integration.md @@ -0,0 +1,34 @@ +LLM 集成与配置体验 + +用户体验(设置 → 连接测试 → 首次成功) +- 模型选择以结果导向文案呈现: + - 最智能(性能优先) + - 情感理解好(情感/社交优化) + - 隐私更强(本地/自建推理) +- 输入 API Key/Endpoint/模型名;“测试连接”触发一次最小请求并显示: + - 成功/失败、耗时、流式延迟估计;失败提供可执行修复建议(网络/权限/额度)。 +- 支持多配置并可设默认;星标为默认模型。 + +架构与接口 +- Provider 插件化:OpenAI、Anthropic、本地 Oobabooga(text-generation-webui 或 OpenAI-compatible)。 +- 统一接口: + - createClient(config) + - streamCompletion({ system, messages, tools?, temperature?, maxTokens? }) → AsyncIterable + - embeddings?(input) → number[] +- 速率限制与并发队列:防止 HUD 产生的多路请求拥塞;错误可重试。 + +提示工程(简要) +- 系统提示由三部分组成: + 1) 人物档案摘要(昵称、关系、偏好、禁忌)。 + 2) 对话上下文摘要(最近 N 轮关键句)。 + 3) 目标策略(提高好感/婉拒/推进邀约等)。 +- 建议卡模板:标题/一句话/标签/风险提示/效果预测;可 A/B 测试多个模板。 + +本地化与隐私 +- 本地模型:通过 HTTP 兼容接口访问(例如 Oobabooga/openai-compatible);默认关闭遥测。 +- 网络代理:支持自定义代理;建议在下载大模型/资源前执行 `dl1` 启用代理以加速。 + +错误与降级 +- 当云端失败:自动切至备用 Provider;或退化为“建议模板库 + 关键词检索”模式。 + + diff --git a/desktop/spec/prd-desktop.md b/desktop/spec/prd-desktop.md new file mode 100644 index 0000000..4ff8c4b --- /dev/null +++ b/desktop/spec/prd-desktop.md @@ -0,0 +1,60 @@ +桌面版 PRD(基于 Core User Journey) + +目标与定位 +- 目标用户:希望提升与特定对象沟通质量、获得实时指导与复盘的用户(如 Persona“小明”)。 +- 核心定位:在桌面聊天场景中提供“实时辅助 + 即时反馈 + 复盘学习”的完整闭环。 +- 平台:Electron(Windows/macOS),须双平台可打包与运行。 +- 非目标:不提供摄像头相关功能;不内置聊天平台(对现有微信/QQ/Discord 等进行并行辅助)。 + +核心用户旅程(桌面化) +阶段一 准备就绪(Onboarding) +1. 启动与权限 + - 仅请求:麦克风、系统音频(通过屏幕录制/桌面捕获)。逐步解释“为什么需要”,支持稍后再说。 + - Aha 时刻:连接测试成功提示;音频测试听写可见。 +2. 引导式配置 + - 选择“AI 大脑”:用结果导向标签(最智能/情感更好/隐私更强)映射到不同模型与推理路径。 + - 输入 API Key/本地端点后可“测试连接”,即时反馈成功与延迟/速率。 +3. 创建对话存档 + - 建立“人物档案”:昵称、关系标签(如“暧昧对象”)、备注(偏好、禁忌、背景信息)。 + - 备注直接作为系统提示工程的一部分以提升效果。 + +阶段二 实时战场(In-Conversation) +1. HUD 无缝融入 + - 点击“开始对话”后,主窗口最小化/驻留托盘;屏幕角落出现半透明 HUD。 + - HUD 不抢焦点,可鼠标穿透/置顶切换;跟随当前活动应用自动调暗/收起。 +2. 实时转录 + - 左侧:对方(系统音频/通话)转写;右侧:用户(麦克风)转写。 + - 支持 VAD、去噪、分段;最近 30-60 秒可滚动回看;关键词高亮。 +3. AI 建议触发(Magic Moment) + - 基于对方语句意图识别(问候/邀约/抱怨/转场等)和目标(增进好感/转移话题/婉拒)。 + - 生成 2-3 张“建议卡片”:标题+一句话+标签+效果预测(❤️±X)。 + - 选择后进入编辑框,可一键复制/朗读提示(可选 TTS)。 +4. 即时反馈 + - 监听用户发送或口述完成后,HUD 播放好感度动画(从 50→70,绿色“+20”上浮)。 + - HUD 显示轻量解释“为什么有效”,强化正反馈。 + +阶段三 复盘进化(Post-Conversation) +1. 数据沉淀 + - 主窗体“人物卡”展示对话次数、当前好感度、最近一次时间。 +2. 可视化进程 + - 人物详情含“好感度变化曲线”“关键事件时间轴(触发点/建议卡/结果)”。 +3. AI 复盘分析 + - 结构化报告:做得好/可改进/下次建议;可导出或收藏为“提示模板”。 + +成功指标(北极星与 KPI) +- Onboarding 完成率(安装→连接测试→音频测试→创建首个存档)≥ 70% +- 首次 Real-time 建议触发率 ≥ 60%,建议被采用率 ≥ 40% +- 单次对话后留存(次日/7 日)≥ 30%/15% +- 复盘页打开率 ≥ 50%,报告完整阅读率 ≥ 30% + +范围与约束 +- 不采集摄像头;不读取通讯录;仅在本地存储对话转写和评分(可选云同步)。 +- macOS 需屏幕录制权限用于系统音频;Windows 通过 desktopCapturer + WASAPI。 +- 模型可选:云端(OpenAI/Anthropic 等)、本地(如 Oobabooga/LLM 推理服务);需可切换与默认选择。 + +验收标准(MVP) +- Win/mac 可安装运行;首次引导含连接测试与音频测试。 +- HUD 能在微信/Discord 上稳定悬浮,展示双通道转写与建议卡。 +- 好感度动画与结果记录可回看;复盘页可生成基础结构化报告。 + + diff --git a/desktop/spec/privacy-and-permissions.md b/desktop/spec/privacy-and-permissions.md new file mode 100644 index 0000000..83d5e7e --- /dev/null +++ b/desktop/spec/privacy-and-permissions.md @@ -0,0 +1,30 @@ +隐私、权限与安全 + +原则 +- 最小权限:仅在需要时请求;不请求摄像头。 +- 本地优先:默认本地存储;敏感信息加密;云端同步为可选。 +- 透明可控:清晰说明用途;提供一键清除与导出。 + +权限(按平台) +- 麦克风(Win/mac):用于采集用户语音与转写。 +- 屏幕录制(macOS):用于获取系统音频轨(通过屏幕共享音频),不采集视频帧。 +- 全局快捷键(可选):需要监听系统级快捷键时。 + +数据处理 +- 对话转写、事件与评分存储在本地 SQLite。 +- API Key/Token 存 OS Keychain(macOS)或 DPAPI(Windows)。 +- 日志不包含原始对话文本(默认);调试模式下经用户同意后可临时启用。 + +联网与模型 +- 云端模型:仅发送必要上下文(摘要+最近窗口);提供“离线/本地模式”。 +- 代理与加速:支持用户自定义代理;下载大文件前给出提示,可使用多进程分片。 + +用户控制 +- 一键暂停监听(HUD/托盘);暂停期间不捕获音频。 +- 数据管理:删除某次对话/某人全部历史;导出 JSON/CSV 报告。 +- 权限管理:设置页展示已授予权限与撤销指引。 + +合规(参考) +- 遵循 GDPR/CCPA 的最小化与可删除权;遵循平台隐私政策与签名要求。 + + diff --git a/desktop/spec/tech-architecture.md b/desktop/spec/tech-architecture.md new file mode 100644 index 0000000..fdf3ddc --- /dev/null +++ b/desktop/spec/tech-architecture.md @@ -0,0 +1,57 @@ +技术架构(Electron Win/mac) + +总体分层 +- 应用壳(Electron) + - Main 进程:窗口与菜单管理、托盘、协议拦截、系统权限与设备枚举、快捷键注册、持久化层、更新与崩溃上报、后台任务调度。 + - Renderer 渲染器:主窗体(仪表盘/存档/复盘)、HUD 浮窗、设置页、模型配置页。 + - Preload:安全桥接(contextIsolation),暴露受控 API(音频设备列表、录制控制、模型推理调用、DB 访问)。 +- 实时能力 + - 音频采集层:麦克风(getUserMedia)与系统音频(desktopCapturer/屏幕共享音轨/虚拟声卡),统一为标准 MediaStream。 + - 转写 ASR:可插拔(本地 Whisper/WebRTC-ASR、远端流式 ASR)。通过 WebWorker/Node Worker Threads 进行推理或数据编解码。 + - 语义分析与建议生成:LLM 推理(流式),意图分类、策略模板填充、效果预测打分。 +- 业务域 + - 人物与对话:存档、会话片段、事件(建议卡触发/采纳/得分)。 + - 评分与可视化:好感度曲线、事件时间轴。 +- 存储 + - SQLite(better-sqlite3)作为本地嵌入式数据库;附件(音频片段、报表导出)走应用数据目录。 + - 可选云同步:基于用户账户登录的增量上传(非 MVP)。 +- UI 技术 + - 前端:React + Vite(或 Next.js in SPA 模式),Tailwind/UNO CSS;可复用移动端设计语言但为桌面优化尺寸与布局。 + +关键模块与边界 +- WindowManager(Main) + - 创建/销毁主窗体与 HUD;控制置顶、鼠标穿透、圆角与阴影。 + - 托盘菜单:开始/停止对话、快速切换人物、静音/监听状态。 +- AudioController(Renderer + Preload 桥接) + - 枚举设备;启动/停止录制;回调分发(VAD 边界、RMS 音量、丢包)。 + - Windows:desktopCapturer 选择“全部屏幕 + 系统音频”;macOS:要求屏幕录制权限后获取系统音轨。 +- AsrService + - 输入:双通道音频帧(mic/system);输出:带时间戳的转写段落。 + - 可配置:本地 vs 远端;采样率、分段策略、语言。 +- NlpService / LlmService + - LLM 供应商插件:OpenAI、Anthropic、本地 Oobabooga(HTTP 推理接口)。 + - 统一流式接口(SSE/WebSocket/HTTP chunked)与重试/超时/速率限制。 +- SuggestionEngine + - 触发条件:意图分类、关键词、对话上下文变化。 + - 输出卡片:标题、文案、标签、效果预测分值、追问建议。 +- FeedbackEngine + - 评分规则:基于用户采纳与对话后续反馈;为 HUD 播放动画提供数值与文案。 +- DataLayer(SQLite) + - 表:person、conversation、turn、event、score、model_profile、settings(详见 data-model.md)。 + - 统一事务与迁移;预编译语句;导入/导出。 + +安全与隔离 +- 启用 contextIsolation、关闭 nodeIntegration;仅通过 preload 暴露白名单 API。 +- CSP 与内容来源白名单;敏感配置(API Key)加密存储(OS Keychain/DPAPI)。 +- 自动更新与签名校验(electron-updater + code sign)。 + +性能与稳定性 +- 音频与 ASR 在独立线程/进程执行,避免阻塞 UI。 +- 流水线背压:ASR 输出分段驱动 LLM;超时与丢包处理。 +- 日志分级:主进程/渲染器/服务各自记录并汇聚(可选 Sentry)。 + +可扩展性 +- Provider 插件:新增 LLM/ASR 仅需实现统一接口。 +- 建议卡模板:以 JSON/Prompt 模板化,可热更新。 + + diff --git a/desktop/spec/test-plan.md b/desktop/spec/test-plan.md new file mode 100644 index 0000000..53d78d3 --- /dev/null +++ b/desktop/spec/test-plan.md @@ -0,0 +1,38 @@ +测试计划与验收标准 + +环境矩阵 +- Windows 10 19045、Windows 11;x64(arm64 可选) +- macOS 12/13/14;Intel/Apple Silicon + +功能用例 +1) 安装与首次启动 + - 成功安装并启动,无报错;显示 Onboarding 向导。 +2) 权限与设备 + - Win:请求麦克风权限;系统音频可捕获。 + - mac:首次捕获系统音频触发屏幕录制权限;授权后成功。 + - 设备枚举、切换麦克风/输出设备成功。 +3) 连接测试 + - OpenAI/Anthropic/本地端点分别测试成功;错误信息可读且包含修复建议。 +4) 音频测试 + - 说话被准确转写;播放音乐/通话可见系统音轨波形与转写(如能)。 +5) 创建人物与对话 + - 新建人物、填写备注后保存;开始对话进入 HUD。 +6) HUD 实时体验 + - 双通道转写正常;建议卡在 2-3 秒内出现;可复制/插入;快捷键生效。 + - 反馈动画连贯;数值记录入库。 +7) 复盘页 + - 曲线正确;事件时间轴显示建议卡触发/采纳;生成结构化报告。 +8) 数据管理 + - 删除单次对话;导出/导入 JSON;API Key 加密保存。 +9) 稳定性 + - 长时间会话(≥30 分钟)CPU/内存可控;丢包/网络抖动下自动恢复。 + +性能门槛(MVP) +- HUD 展开/收起 ≤ 120ms +- 建议卡首字节 ≤ 2.0s(云端),≤ 3.0s(本地模型) +- 进程常驻内存:空闲 < 300MB;会话中 < 800MB(视模型与本地 ASR 而定) + +验收 +- 通过所有功能用例;未解决的高优缺陷为 0;签名/公证通过;打包物可在两平台安装与运行。 + + diff --git a/desktop/spec/ui-design-01-chat-window.md b/desktop/spec/ui-design-01-chat-window.md new file mode 100644 index 0000000..0abf7ca --- /dev/null +++ b/desktop/spec/ui-design-01-chat-window.md @@ -0,0 +1,460 @@ +# UI 设计文档:对话窗口(聊天界面) + +> **原型图参考**:Image 1 - 与 Evelyn 的对话窗口 +> **对应功能**:实时对话 HUD,AI 建议生成与选择 + +--- + +## 1. 整体布局 + +### 1.1 窗口属性 +- **窗口类型**:半透明浮动窗口(Always-on-Top) +- **默认尺寸**:宽 440px,高度自适应内容(最小 600px,最大 800px) +- **背景**:深灰色磨砂半透明 `rgba(68, 68, 68, 0.95)`,带 `backdrop-filter: blur(20px)` +- **圆角**:16px +- **阴影**:`0 8px 32px rgba(0, 0, 0, 0.4)` +- **可拖拽**:标题栏区域可拖动整个窗口 +- **位置记忆**:关闭后记住上次位置,下次启动时恢复 + +### 1.2 布局结构(从上到下) +``` +┌─────────────────────────────────────────┐ +│ [标题栏] 对话: Evelyn [-][□][×] │ ← 高度 48px +├─────────────────────────────────────────┤ +│ │ +│ [对话气泡区域] │ ← 可滚动,flex-grow +│ └─ 左侧气泡(对方) │ +│ └─ 右侧气泡(自己) │ +│ └─ 标星标记(重要内容) │ +│ │ +├─────────────────────────────────────────┤ +│ [AI 推荐区域] │ ← 高度自适应(0-240px) +│ "你似乎对此了解很多..." │ +│ └─ [拒绝 按钮] [暧昧回应 按钮] │ +│ "这是一条危险的路..." │ +│ └─ [中性 按钮] [创意 按钮] │ +│ │ +├─────────────────────────────────────────┤ +│ AI 思考中... │ ← 加载状态,高 32px +├─────────────────────────────────────────┤ +│ [底部操作栏] │ ← 高度 64px +│ [😊] [⏸] [✨] [⚙] │ +└─────────────────────────────────────────┘ +``` + +--- + +## 2. 标题栏(Title Bar) + +### 2.1 视觉规范 +- **高度**:48px +- **背景**:渐变 `linear-gradient(135deg, #D91B5C 0%, #C2185B 100%)`(品牌粉红色) +- **文字**:白色,字体 16px,粗体(font-weight: 600) +- **格式**:`对话: {角色名称}`(左对齐,左边距 16px) +- **右侧按钮**: + - 最小化 `−`、最大化 `□`、关闭 `×` + - 尺寸:每个 32×32px,hover 时背景变为 `rgba(255,255,255,0.2)` + - 间距:按钮间距 4px,右边距 8px + +### 2.2 交互行为 +- **拖拽**:鼠标在标题栏任意位置按下可拖动窗口 +- **双击**:双击标题栏切换最大化/还原 +- **最小化**:窗口缩小到托盘/任务栏,对话继续监听 +- **关闭**:弹出确认对话框 "是否保存并退出对话?",有 "保存"/"放弃"/"取消" 三个选项 + +--- + +## 3. 对话气泡区域 + +### 3.1 容器属性 +- **背景**:透明 +- **内边距**:上下 16px,左右 16px +- **滚动**:`overflow-y: auto`,自定义滚动条样式(宽 6px,深灰色轨道,浅灰色滑块) +- **排列**:垂直 Flexbox,从上到下 + +### 3.2 对方消息气泡(左侧) +``` +┌────────────────────────────────┐ +│ 没想到今晚会在这里见到你。 │ +├────────────────────────────────┤ +│ 这个城市充满了惊喜,不是吗? │ +└────────────────────────────────┘ +``` + +**样式规范**: +- **对齐**:左对齐(margin-right: auto) +- **最大宽度**:70% +- **背景色**:`rgba(100, 116, 139, 0.3)`(浅灰蓝半透明) +- **文字颜色**:`#E2E8F0`(浅灰白) +- **字体**:14px,行高 1.6 +- **内边距**:12px 16px +- **圆角**: + - 左上 4px,右上 16px + - 左下 16px,右下 16px + - (模拟对话气泡的"尾巴"在左上) +- **间距**:相邻气泡间距 8px +- **连续消息**:如果连续多条来自同一方,第一条用完整圆角,中间条用统一圆角 12px,最后一条用"尾巴"圆角 + +### 3.3 自己的消息气泡(右侧) +``` + ┌────────────────────────────┐ + │ 听起来真不错!也可以这么说,│ + │ 我听说你在调查"猩红集团"的 │ + │ 案子。 ⭐│ + └────────────────────────────┘ +``` + +**样式规范**: +- **对齐**:右对齐(margin-left: auto) +- **最大宽度**:70% +- **背景色**:`rgba(217, 27, 92, 0.2)`(品牌粉色半透明) +- **文字颜色**:`#FFF`(纯白) +- **字体**:14px,行高 1.6 +- **内边距**:12px 16px +- **圆角**: + - 左上 16px,右上 4px + - 左下 16px,右下 16px + - ("尾巴"在右上) +- **星标标记**: + - 位置:右下角,绝对定位 `bottom: 4px, right: 4px` + - 图标:⭐(红色五角星,尺寸 16px) + - 显示条件:该消息被 AI 标记为"攻略关键点"时显示 + - Hover 提示:显示 tooltip "关键进展:好感度 +20" + +### 3.4 消息元数据 +- **时间戳**:每条消息气泡下方居中显示,格式 `14:32` +- **样式**:10px,颜色 `rgba(255,255,255,0.4)`,上边距 4px +- **合并规则**:1 分钟内的连续消息不重复显示时间 + +--- + +## 4. AI 推荐区域 + +### 4.1 整体布局 +- **触发时机**:检测到对方说话结束后 2 秒,从底部滑入动画(300ms ease-out) +- **背景**:`rgba(30, 30, 30, 0.95)`,顶部有 1px 分隔线 `rgba(255,255,255,0.1)` +- **内边距**:16px +- **最大高度**:240px,超出部分可滚动 + +### 4.2 推荐卡片结构 +每个推荐选项为一个卡片,结构如下: + +``` +┌────────────────────────────────────────┐ +│ "你似乎对此了解很多。愿意分享一下吗?" │ ← 建议文本 +│ │ +│ [拒绝] [暧昧回应] │ ← 标签按钮 +└────────────────────────────────────────┘ +``` + +**卡片样式**: +- **背景**:`rgba(51, 65, 85, 0.6)`(深蓝灰) +- **圆角**:12px +- **内边距**:12px 16px +- **边框**:1px solid transparent(默认),hover 时变为 `rgba(217, 27, 92, 0.5)` +- **间距**:卡片之间 12px +- **过渡**:所有 hover 效果 200ms ease + +### 4.3 建议文本 +- **字体**:15px,行高 1.5 +- **颜色**:`#FFF` +- **最大行数**:3 行,超出显示省略号 +- **引号**:使用中文双引号 `" "` + +### 4.4 标签按钮 +- **布局**:Flexbox 横向排列,`justify-content: space-between` +- **单个按钮**: + - **尺寸**:高 32px,宽度自适应(最小 80px),水平内边距 16px + - **背景色**:根据标签类型变化: + - 拒绝:`rgba(239, 68, 68, 0.2)` + 红色边框 + - 暧昧回应:`rgba(236, 72, 153, 0.3)` + 粉色边框 + - 中性:`rgba(148, 163, 184, 0.2)` + 灰色边框 + - 创意:`rgba(59, 130, 246, 0.3)` + 蓝色边框 + - **文字**:12px,粗体 500,颜色对应标签色(高亮版本) + - **圆角**:6px + - **Hover**:背景不透明度增加到 0.5,指针变为 pointer + - **点击效果**:按钮缩放 0.95,100ms 后恢复 + +### 4.5 效果预测徽章(可选) +- **位置**:在标签按钮右侧,间距 8px +- **格式**:`❤️ +20` 或 `❤️ -5` +- **样式**: + - 字体 11px + - 正值:绿色 `#10B981` + - 负值:橙色 `#F59E0B` + - 背景:`rgba(0,0,0,0.3)`,圆角 4px,内边距 2px 6px + +### 4.6 关闭按钮 +- **位置**:推荐区域右上角 +- **图标**:`×`,尺寸 20px +- **样式**:颜色 `rgba(255,255,255,0.5)`,hover 时 `#FFF` +- **行为**:点击后推荐区域向下滑出消失(300ms),用户可以手动输入 + +--- + +## 5. AI 状态指示器 + +### 5.1 思考状态 +``` +AI 思考中... +``` +- **显示时机**:对方消息结束后,AI 生成建议的等待期间 +- **位置**:推荐区域上方,独立一行 +- **背景**:`rgba(59, 130, 246, 0.1)`(淡蓝色) +- **高度**:32px +- **文字**: + - 内容:`AI 思考中...`(带动画三个点循环显示) + - 字体:13px,斜体 + - 颜色:`#60A5FA`(淡蓝色) + - 居中对齐 +- **动画**:脉冲效果(opacity 0.6 ↔ 1.0,1.5s 循环) + +### 5.2 错误状态 +- **显示时机**:AI 调用失败或超时(5 秒) +- **样式**: + - 背景变为 `rgba(239, 68, 68, 0.15)`(淡红色) + - 文字:`AI 暂时无法响应,请检查网络或稍后重试` + - 颜色:`#F87171`(红色) + - 右侧显示"重试"按钮(小型按钮,60px 宽) + +--- + +## 6. 底部操作栏 + +### 6.1 布局 +- **高度**:64px +- **背景**:`rgba(30, 30, 30, 0.95)`,顶部 1px 分隔线 +- **内边距**:16px +- **布局**:Flexbox 横向居中排列,`gap: 24px` + +### 6.2 按钮定义 +``` +[😊] [⏸] [✨] [⚙] +情绪 暂停监听 AI助手 设置 +``` + +#### 按钮通用样式 +- **尺寸**:48×48px 圆形按钮 +- **背景**:`rgba(100, 116, 139, 0.3)` +- **图标**:24×24px,颜色 `#E2E8F0` +- **Hover**:背景变为 `rgba(217, 27, 92, 0.5)`,图标放大 1.1 倍(200ms 过渡) +- **Active**:背景变为品牌粉色 `#D91B5C`,图标为白色 + +#### 各按钮功能 +1. **情绪按钮(😊)**: + - 点击展开情绪表情选择器(emoji picker) + - 用途:手动标记当前对话的情绪氛围 + - 选择器:弹出在按钮上方,5×4 网格,包含常用表情 + +2. **暂停/播放按钮(⏸/▶)**: + - 切换监听状态 + - 暂停时:图标变为 `▶`,背景变为橙色 `#F59E0B` + - 作用:临时停止音频捕获和 AI 分析 + +3. **AI 助手按钮(✨)**: + - 点击手动触发 AI 建议生成 + - 作用:当 AI 未自动响应时,用户可以主动请求建议 + - 状态:生成中时旋转动画(360° 无限循环) + +4. **设置按钮(⚙)**: + - 点击打开快速设置菜单(下拉弹窗) + - 包含: + - 切换对话对象 + - 调整透明度(滑块 50%-100%) + - 快捷键设置 + - 返回主窗口 + +--- + +## 7. 交互动画与过渡 + +### 7.1 消息发送动画 +1. 用户参考建议后说话 +2. 新气泡从右侧 150% 位置滑入(300ms ease-out) +3. 同时透明度 0 → 1 +4. 如果是关键进展,星标延迟 500ms 后闪烁出现(scale 0.5 → 1.2 → 1.0) + +### 7.2 好感度变化动画 +- **触发**:AI 判定产生好感度变化时 +- **动画**: + 1. 在相关消息气泡上方飘出数字 `+20` + 2. 颜色为绿色 `#10B981`,字体 18px 粗体 + 3. 向上移动 40px,同时透明度 1 → 0(1000ms) + 4. 移动曲线使用 `cubic-bezier(0.25, 0.46, 0.45, 0.94)` + +### 7.3 推荐区域滑入/滑出 +- **滑入**:从 `translateY(100%)` 到 `translateY(0)`,300ms ease-out +- **滑出**:从 `translateY(0)` 到 `translateY(100%)`,300ms ease-in +- **高度过渡**:max-height 从 0 到实际高度(300ms) + +--- + +## 8. 响应式与状态管理 + +### 8.1 窗口尺寸变化 +- **最小宽度**:360px +- **最小高度**:480px +- **缩放规则**: + - 气泡最大宽度按比例调整(始终为容器宽度的 70%) + - 推荐区域在宽度 < 400px 时,标签按钮堆叠为单列 + +### 8.2 焦点状态 +- **窗口失焦**:整体透明度降低到 0.8 +- **窗口重新获得焦点**:透明度恢复到 1.0(200ms 过渡) + +### 8.3 深色/浅色模式(预留) +当前仅支持深色模式,未来可扩展浅色主题变量: +```css +--bg-primary: rgba(68, 68, 68, 0.95); +--text-primary: #E2E8F0; +--accent-color: #D91B5C; +``` + +--- + +## 9. 无障碍(Accessibility) + +### 9.1 键盘导航 +- **Tab 键**:按钮间循环聚焦 +- **Enter/Space**:激活聚焦的按钮 +- **Esc**:关闭推荐区域或设置菜单 +- **Ctrl+W**:关闭窗口(弹出确认) + +### 9.2 屏幕阅读器 +- 所有按钮添加 `aria-label` 属性 +- 消息气泡添加 `role="log"` 和 `aria-live="polite"` +- 推荐卡片添加 `role="option"` 和 `aria-describedby` + +### 9.3 对比度 +- 所有文字与背景对比度 ≥ 4.5:1(符合 WCAG AA 标准) +- 交互元素对比度 ≥ 3:1 + +--- + +## 10. 技术实现提示 + +### 10.1 React 组件结构建议 +```jsx + + + + {messages.map(msg => ( + msg.role === 'user' + ? + : + ))} + + {aiState === 'thinking' && } + {suggestions.length > 0 && ( + + )} + + +``` + +### 10.2 样式方案 +- 使用 **Tailwind CSS** + 自定义主题变量 +- 或使用 **Styled Components** 实现动态主题切换 +- 动画使用 **Framer Motion** 库简化实现 + +### 10.3 性能优化 +- 消息列表使用虚拟滚动(react-window),支持 1000+ 条消息 +- 推荐卡片懒加载,仅渲染可见区域 +- 防抖用户输入事件(300ms) + +--- + +## 11. 设计资源清单 + +### 11.1 图标资源 +- 推荐使用 **Lucide Icons** 或 **Heroicons** +- 需要的图标: + - Minimize, Maximize, Close + - Smile (Emoji), Pause, Play, Sparkles, Settings + - Star (filled/outline) + - X (close small) + +### 11.2 字体 +- **主字体**:思源黑体(Noto Sans SC)或 苹方(PingFang SC) +- **等宽字体**(代码/时间戳):JetBrains Mono + +### 11.3 颜色变量表 +```css +:root { + /* 品牌色 */ + --brand-primary: #D91B5C; + --brand-gradient: linear-gradient(135deg, #D91B5C 0%, #C2185B 100%); + + /* 背景 */ + --bg-window: rgba(68, 68, 68, 0.95); + --bg-panel: rgba(30, 30, 30, 0.95); + --bg-card: rgba(51, 65, 85, 0.6); + + /* 气泡 */ + --bubble-partner: rgba(100, 116, 139, 0.3); + --bubble-user: rgba(217, 27, 92, 0.2); + + /* 文字 */ + --text-primary: #FFFFFF; + --text-secondary: #E2E8F0; + --text-muted: rgba(255, 255, 255, 0.4); + + /* 标签 */ + --tag-reject: rgba(239, 68, 68, 0.2); + --tag-flirt: rgba(236, 72, 153, 0.3); + --tag-neutral: rgba(148, 163, 184, 0.2); + --tag-creative: rgba(59, 130, 246, 0.3); + + /* 状态 */ + --status-success: #10B981; + --status-warning: #F59E0B; + --status-error: #F87171; + --status-info: #60A5FA; +} +``` + +--- + +## 12. 验收标准 + +### 12.1 视觉还原度 +- [ ] 窗口圆角、阴影与原型一致 +- [ ] 品牌色渐变在标题栏正确显示 +- [ ] 消息气泡"尾巴"圆角正确(左/右差异) +- [ ] 星标图标仅在关键消息显示 +- [ ] 推荐卡片标签颜色与原型匹配 + +### 12.2 交互流畅性 +- [ ] 窗口拖拽无卡顿(60fps) +- [ ] 消息滑入动画自然(300ms 以内) +- [ ] 好感度数字飘出动画清晰可见 +- [ ] 推荐区域滑入/滑出无闪烁 +- [ ] 按钮 hover/active 状态响应 < 100ms + +### 12.3 功能完整性 +- [ ] 消息自动滚动到最新一条 +- [ ] 暂停按钮正确切换音频监听状态 +- [ ] 点击推荐后自动填充到对话 +- [ ] 关闭窗口弹出保存确认 +- [ ] 设置菜单正确打开并响应操作 + +### 12.4 跨平台一致性 +- [ ] Windows 和 macOS 窗口控制按钮位置符合平台规范 +- [ ] 字体渲染在两平台清晰 +- [ ] 透明效果在两平台正常(Windows 使用 Acrylic,macOS 使用 Vibrancy) + +--- + +**文档版本**:v1.0 +**最后更新**:2025-11-12 +**维护者**:产品设计团队 + diff --git a/desktop/spec/ui-design-02-llm-config.md b/desktop/spec/ui-design-02-llm-config.md new file mode 100644 index 0000000..a91aff5 --- /dev/null +++ b/desktop/spec/ui-design-02-llm-config.md @@ -0,0 +1,577 @@ +# UI 设计文档:LLM 配置页面(模型选择与连接) + +> **原型图参考**:Image 2 - LLM 配置管理界面 +> **对应功能**:AI 模型选择、API Key 输入、连接测试、模型启用/禁用 + +--- + +## 1. 整体布局 + +### 1.1 页面属性 +- **页面类型**:主窗口内容区域(非浮窗) +- **尺寸**:全屏适配,宽度 1200px~,高度 800px~ +- **背景**:浅白色 `#F5F7FA`(桌面应用通常背景较亮) +- **滚动**:页面内容超高时支持垂直滚动 + +### 1.2 结构布局(从上到下) +``` +┌──────────────────────────────────────────────────────┐ +│ [侧边栏导航] [主内容区] │ +│ │ +│ ◆ 总览 LLM 配置 │ +│ ◆ 攻略对象 管理你的 AI 模型配置,添加新模型并设置默认值。 +│ ◆ 对话编辑器 +│ ◆ LLM配置(✓) ┌──────────────────────────────┐ +│ ◆ 设置 │ + 添加新模型 [组件预留] │ +│ └──────────────────────────────┘ +│ +│ ┌──────────────────────────────┐ +│ [帮助] │ GPT-4o (OpenAI) │ +│ [报告问题] │ ● 已激活 │ +│ [登出] │ ┌──────────────────────────┐ │ +│ │ │ gpt-4o │ │ +│ │ │ OpenAI 官方最强模型 │ │ +│ │ │ [编辑] [删除] │ │ +│ │ └──────────────────────────┘ │ +│ │ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ +│ └──────────────────────────────┘ +│ +│ ┌──────────────────────────────┐ +│ │ Claude 3 Opus (Anthropic) │ +│ │ ◯ 未激活 │ +│ │ ┌──────────────────────────┐ │ +│ │ │ claude-3-opus-... │ │ +│ │ │ 情感理解更好的模型 │ │ +│ │ │ [编辑] [连接测试] │ │ +│ │ └──────────────────────────┘ │ +│ │ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ +│ └──────────────────────────────┘ +│ +│ ┌──────────────────────────────┐ +│ │ 本地 Llama 3 (Oobabooga) │ +│ │ ⚠ 连接错误 │ +│ │ ┌──────────────────────────┐ │ +│ │ │ 本地 Oobabooga │ │ +│ │ │ 隐私友好的本地模型 │ │ +│ │ │ [编辑] [重新连接] │ │ +│ │ └──────────────────────────┘ │ +│ │ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ +│ └──────────────────────────────┘ +│ +│ [+ 添加你的第一个 AI 模型] +│ 从头开始一段新对话。连接到 OpenAI、 +│ Anthropic 或本地实例。 +│ +└──────────────────────────────────────────────────────┘ +``` + +--- + +## 2. 侧边栏导航 + +### 2.1 容器属性 +- **宽度**:280px(固定) +- **背景**:品牌粉红色渐变 `linear-gradient(135deg, #D91B5C 0%, #C2185B 100%)` +- **高度**:100vh +- **内边距**:20px 0 +- **位置**:绝对定位或 Flexbox 容器左侧 +- **滚动**:如果导航项超过高度,启用滚动 + +### 2.2 导航项结构 +``` +◆ 总览 +◆ 攻略对象 +◆ 对话编辑器 +◆ LLM配置 ✓ +◆ 设置 +``` + +**每个导航项样式**: +- **文字颜色**: + - 默认:`rgba(255, 255, 255, 0.7)` + - Hover:`#FFF` + - Active(当前页):`#FFF` +- **字体**:15px,行高 2.0(项目间距 30px) +- **左内边距**:24px(缩进) +- **背景**: + - 默认:透明 + - Hover:`rgba(255, 255, 255, 0.1)` + - Active:`rgba(255, 255, 255, 0.2)`,左边有 3px 白色竖线指示器 +- **图标**:每项前有小菱形 `◆`(24px 从左边距),颜色与文字同步 + +### 2.3 底部功能区(stickied 到底部) +``` +[帮助图标] 帮助 +[报告图标] 报告问题 +[登出图标] 登出 +``` + +- **位置**:绝对定位 `bottom: 20px, left: 0, right: 0` +- **样式**: + - 背景:`rgba(255, 255, 255, 0.1)` + - 边框顶部:1px solid `rgba(255, 255, 255, 0.2)` + - 内边距:16px 24px + - 每项高 40px,间距 8px +- **文字**:13px,颜色 `rgba(255, 255, 255, 0.7)` +- **图标**:左侧 16×16px,右侧箭头 (→) +- **Hover**:背景变为 `rgba(255, 255, 255, 0.15)`,文字变白 + +--- + +## 3. 顶部标题栏(主内容区上方) + +### 3.1 标题栏布局 +``` +LLM 配置 [+ 添加新模型] +管理你的 AI 模型配置,添加新模型并设置默认值。 +``` + +- **高度**:80px +- **背景**:白色 `#FFFFFF` +- **边框**:底部 1px solid `#E5E7EB` +- **内边距**:20px 40px +- **布局**:Flexbox 列方向 + +### 3.2 标题 +- **文字**:`LLM 配置` +- **字体**:28px,粗体 700,颜色 `#1F2937` +- **下边距**:8px + +### 3.3 副标题 + 操作按钮 +- **布局**:Flex row,`justify-content: space-between` +- **副标题**: + - 文字:`管理你的 AI 模型配置,添加新模型并设置默认值。` + - 字体:14px,颜色 `#6B7280` +- **按钮**:`+ 添加新模型` + - 背景:品牌粉红 `#D91B5C` + - 文字:白色,14px 粗体 + - 内边距:10px 20px + - 圆角:8px + - Hover:背景变深 `#C2185B` + - 阴影:`0 2px 4px rgba(0,0,0,0.1)` + +--- + +## 4. 模型卡片(核心内容) + +### 4.1 卡片容器 +``` +┌─────────────────────────────────────────────────┐ +│ GPT-4o (OpenAI) │ +│ ● 已激活 │ +│ ┌────────────────────────────────────────────┐ │ +│ │ gpt-4o │ │ +│ │ OpenAI 官方最强模型。准确率 98%+,适合 │ │ +│ │ 多轮对话与情感分析。 │ │ +│ │ [编辑] [连接测试] │ │ +│ └────────────────────────────────────────────┘ │ +│ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ +└─────────────────────────────────────────────────┘ +``` + +**卡片样式**: +- **背景**:白色 `#FFFFFF` +- **圆角**:12px +- **边框**:1px solid `#E5E7EB` +- **阴影**:`0 1px 3px rgba(0, 0, 0, 0.1)`,hover 时升高到 `0 4px 12px rgba(0, 0, 0, 0.15)` +- **内边距**:20px +- **间距**:卡片之间 16px +- **过渡**:所有变化 200ms ease +- **最大宽度**:800px(居中对齐) + +### 4.2 卡片头部(标题 + 状态) +- **布局**:Flex row,`justify-content: space-between, align-items: center` +- **左侧**: + - **标题**:`GPT-4o (OpenAI)`,字体 18px 粗体 600,颜色 `#1F2937` +- **右侧**: + - **状态指示器**(3 种): + 1. 已激活:`● 已激活`,颜色绿色 `#10B981` + 2. 未激活:`◯ 未激活`,颜色灰色 `#9CA3AF` + 3. 连接错误:`⚠ 连接错误`,颜色红色 `#EF4444` + - 字体:13px,粗体 500 + - 右边距:0px + +### 4.3 卡片主体(模型信息) +- **背景**:`#F9FAFB`(极浅灰) +- **圆角**:8px +- **内边距**:12px 16px +- **字体**:13px,行高 1.6 +- **内容**: + - 第一行:模型 ID(`gpt-4o`),字体粗体 500,颜色 `#1F2937` + - 第二行:模型描述(`OpenAI 官方最强模型...`),颜色 `#6B7280` + - 第三行:按钮组 `[编辑] [连接测试]` 或根据状态显示 + +### 4.4 卡片底部(操作栏) +- **布局**:Flex row,`justify-content: flex-start, gap: 16px` +- **操作按钮**(4 个,按序): + 1. **⚙ 设为默认** + - 尺寸:高 32px,宽 120px + - 背景:透明,边框 1px `#D1D5DB` + - 文字:13px,颜色 `#374151` + - Hover:背景 `#F3F4F6`,边框 `#9CA3AF` + + 2. **☆ 收藏**(未收藏状态)或 **★ 已收藏**(已收藏状态) + - 尺寸:高 32px,宽 100px + - 背景:透明,边框 1px `#D1D5DB` + - 文字:13px + - 未收藏:颜色 `#6B7280` + - 已收藏:颜色 `#FCD34D`(金黄色),边框 `#FCD34D` + + 3. **🗑 删除** + - 尺寸:高 32px,宽 100px + - 背景:透明,边框 1px `#FCA5A5`(淡红) + - 文字:13px,颜色 `#EF4444`(红) + - Hover:背景 `#FEE2E2` + + 4. **编辑按钮**(仅在卡片主体中显示) + - 位置:模型信息底部,与"连接测试"并排 + - 尺寸:高 28px,宽 60px + - 背景:品牌粉色 `#D91B5C` + - 文字:12px,颜色白色 + - 圆角:4px + - Hover:背景 `#C2185B` + +### 4.5 条件渲染规则 +根据模型连接状态,卡片内容和按钮不同: + +| 状态 | 卡片右侧指示器 | 背景色调整 | 可用按钮 | +|------|---|---|---| +| 已激活 | ● 绿色 | 无调整 | 设为默认、收藏、编辑、删除、连接测试 | +| 未激活 | ◯ 灰色 | 轻度灰化 opacity 0.6 | 设为默认、收藏、编辑、删除、连接测试 | +| 连接错误 | ⚠ 红色 | 轻度红化 opacity 0.8 | 设为默认、收藏、编辑、删除、重新连接 | + +--- + +## 5. 模态对话:添加新模型 + +### 5.1 对话框属性 +触发方式:点击"+ 添加新模型"按钮或空卡片上的链接 + +- **宽度**:600px +- **背景**:白色 `#FFFFFF` +- **圆角**:12px +- **阴影**:`0 10px 40px rgba(0, 0, 0, 0.2)` +- **z-index**:确保在所有内容上方 + +### 5.2 对话框结构 +``` +┌────────────────────────────────────────┐ +│ 添加 AI 模型 [×] │ +├────────────────────────────────────────┤ +│ 选择模型供应商: │ +│ ┌──────────────────────────────────┐ │ +│ │ OpenAI (GPT-4o, GPT-3.5...) ▼ │ │ ← Dropdown +│ └──────────────────────────────────┘ │ +│ │ +│ API Key / 认证信息: │ +│ ┌──────────────────────────────────┐ │ +│ │ sk-proj-xxxxx... (粘贴你的 key) │ │ ← Input +│ └──────────────────────────────────┘ │ +│ │ +│ [测试连接] [保存] [取消] │ +└────────────────────────────────────────┘ +``` + +### 5.3 字段定义 + +#### 供应商选择器 +- **标签**:`选择模型供应商:`,14px 粗体 600 +- **下拉框**: + - 选项: + - OpenAI (GPT-4o, GPT-3.5...) + - Anthropic (Claude 3 Opus, Sonnet...) + - 本地 (Ollama, Oobabooga, Vllm...) + - 自定义 API (兼容 OpenAI 格式) + - 高度:40px + - 背景:`#F9FAFB`,边框 1px `#D1D5DB` + - 文字:14px,颜色 `#1F2937` + - 圆角:6px + - 内边距:10px 12px + - Hover:边框 `#9CA3AF` + +#### API Key 输入框 +- **标签**:`API Key / 认证信息:`,14px 粗体 600,下边距 8px +- **输入框**: + - 高度:40px + - 背景:`#F9FAFB`,边框 1px `#D1D5DB` + - 文字:13px,颜色 `#374151` + - 圆角:6px + - 内边距:10px 12px + - Placeholder:`粘贴你的 API Key(仅本地存储,不上传)`,颜色 `#9CA3AF` + - Focus:边框变为品牌粉色 `#D91B5C`,阴影 `0 0 0 3px rgba(217, 27, 92, 0.1)` + - 密码隐藏:启用密码显示切换按钮(👁 符号) + +#### 高级选项(可折叠) +- **折叠标题**:`⚙ 高级选项`,13px,颜色 `#6B7280` +- **展开内容**(不详细列出): + - 模型名称自定义(override 模型 ID) + - 温度系数(Temperature)滑块 0.0 ~ 2.0 + - 最大 Token 数量 + - 代理设置(可选) + +### 5.4 按钮栏 +``` +[测试连接] [保存] [取消] +``` + +- **布局**:Flex row,`justify-content: space-between, align-items: center` +- **左侧**: + - **[测试连接]** 按钮 + - 背景:蓝色 `#3B82F6` + - 文字:白色,13px 粗体 + - 内边距:10px 20px + - 圆角:6px + - Hover:背景 `#2563EB` + - 状态:点击后显示加载动画,完成后显示 "✓ 连接成功" 或 "✗ 连接失败" + +- **右侧**: + - **[保存]** 按钮 + - 背景:品牌粉红 `#D91B5C` + - 文字:白色,13px 粗体 + - 内边距:10px 24px + - 圆角:6px + - Hover:背景 `#C2185B` + - 禁用状态(未测试连接时):opacity 0.5,pointer-events none + + - **[取消]** 按钮 + - 背景:透明,边框 1px `#D1D5DB` + - 文字:`#374151`,13px 粗体 + - 内边距:10px 24px + - 圆角:6px + - Hover:背景 `#F3F4F6` + +### 5.5 测试连接反馈 +- **进行中**:按钮显示加载动画(旋转圆形),文字变为 "测试中..." +- **成功**: + - 按钮变绿 `#10B981` + - 文字:`✓ 连接成功` + - 下方显示绿色提示信息:`API Key 有效,模型 gpt-4o 可用` + - 延迟 1.5 秒后,自动恢复为 "测试连接" 状态 +- **失败**: + - 按钮变红 `#EF4444` + - 文字:`✗ 连接失败` + - 下方显示红色错误信息:`API Key 无效或网络错误:Unauthorized` + - 用户可以点击"查看详情"展开错误日志 + +--- + +## 6. 空状态 + +### 6.1 无模型已配置 +``` +┌────────────────────────────────────────┐ +│ │ +│ [⊕ 大图标] │ +│ │ +│ 尚未配置模型 │ +│ │ +│ 通过添加你的第一个 AI 模型配置并开始。│ +│ 连接到 OpenAI、Anthropic 或本地实例。 │ +│ │ +│ [+ 添加你的第一个 AI 模型] │ +│ │ +└────────────────────────────────────────┘ +``` + +**样式**: +- **背景**:白色卡片,圆角 12px,边框 2px dashed `#E5E7EB` +- **内容对齐**:居中 +- **图标**:`⊕`,尺寸 48px,颜色 `#D1D5DB` +- **标题**:`尚未配置模型`,18px 粗体 600,颜色 `#1F2937`,下边距 8px +- **描述**:14px,颜色 `#6B7280`,行高 1.6 +- **按钮**:同"添加新模型"按钮样式 + +--- + +## 7. 交互动画与过渡 + +### 7.1 卡片加载动画 +- 新增卡片从底部滑入(300ms ease-out) +- 从 `translateY(20px), opacity: 0` 到 `translateY(0), opacity: 1` + +### 7.2 卡片删除动画 +- 向右滑出 + 淡出(300ms ease-in) +- 从 `translateX(0), opacity: 1` 到 `translateX(100px), opacity: 0` +- 删除前弹出确认对话框 + +### 7.3 状态指示灯脉冲 +- **已激活**:绿灯,脉冲动画 `scale: 1 ↔ 1.2`,2s 循环 +- **连接错误**:红灯,不闪烁,持续显示警告状态 + +### 7.4 对话框出现/消失 +- **打开**:从中心缩放 `scale: 0.9, opacity: 0` 到 `scale: 1, opacity: 1`,200ms ease-out +- **关闭**:反向,200ms ease-in +- **背景**:暗灰色 overlay `rgba(0, 0, 0, 0.5)`,同步过渡 + +--- + +## 8. 响应式设计 + +### 8.1 桌面版(≥ 1200px) +- 侧边栏固定宽度 280px +- 卡片最大宽度 800px +- 两列布局(侧栏 + 主内容) + +### 8.2 平板版(768px ~ 1199px) +- 侧边栏可折叠(汉堡菜单 ☰) +- 卡片最大宽度 600px +- 内容区内边距减少到 20px + +### 8.3 移动版(< 768px) +- 应用场景:桌面应用在小窗口下 +- 侧边栏隐藏,导航改为顶部标签栏 +- 卡片 100% 宽度,内边距 16px + +--- + +## 9. 键盘快捷键 & 无障碍 + +### 9.1 快捷键 +- **Tab**:在卡片和按钮间导航 +- **Enter**:激活聚焦元素(按钮、下拉框选项) +- **Escape**:关闭对话框或下拉菜单 +- **Ctrl+K**:快速打开"添加模型"对话框 + +### 9.2 屏幕阅读器支持 +- 所有按钮有 `aria-label` +- 卡片有 `role="region"` 和 `aria-labelledby="card-title"` +- 状态指示器有 `aria-live="polite"` 以通知状态变化 +- 对话框有 `role="dialog"` 和 `aria-modal="true"` + +### 9.3 颜色对比度 +- 所有文字与背景对比度 ≥ 4.5:1(符合 WCAG AA) +- 状态指示灯配备文字标签(不仅依赖颜色) + +--- + +## 10. 技术实现提示 + +### 10.1 React 组件结构 +```jsx + + + + + + {models.map(model => ( + + ))} + + {models.length === 0 && } + + + {showAddModal && ( + + )} + +``` + +### 10.2 状态管理建议 +- 使用 **Zustand** 或 **Jotai** 管理模型列表、当前选中模型 +- 使用 **React Query** 缓存 API 连接测试结果 +- 本地存储使用 **Electron 的 ipcMain** 与主进程通信,加密保存 API Key + +### 10.3 样式方案 +- **Tailwind CSS** + 自定义 Tailwind Config 定义颜色变量 +- **Framer Motion** 处理动画 +- 或使用 **Styled Components** + **react-spring** 实现更细致的动画 + +### 10.4 表单验证 +- API Key 输入框:前端显示 "最少 20 字符" 提示 +- 供应商选择:必选项 +- 测试连接成功后,"保存"按钮才可用 + +--- + +## 11. 设计资源清单 + +### 11.1 图标 +- 菱形 `◆`、圆形 `●`、空心圆 `◯`、警告三角 `⚠` +- 编辑 ✏、删除 🗑、收藏 ☆/★、设置 ⚙、加号 +、右箭头 →、眼睛 👁 + +### 11.2 颜色变量表 +```css +:root { + /* 品牌色 */ + --brand-primary: #D91B5C; + --brand-gradient: linear-gradient(135deg, #D91B5C 0%, #C2185B 100%); + + /* 背景 */ + --bg-page: #F5F7FA; + --bg-card: #FFFFFF; + --bg-input: #F9FAFB; + --bg-sidebar: var(--brand-gradient); + + /* 文字 */ + --text-primary: #1F2937; + --text-secondary: #6B7280; + --text-muted: #9CA3AF; + --text-white: #FFFFFF; + + /* 边框 */ + --border-default: #E5E7EB; + --border-dark: #D1D5DB; + + /* 状态色 */ + --success: #10B981; + --error: #EF4444; + --warning: #F59E0B; + --info: #3B82F6; +} +``` + +### 11.3 字体 +- 主字体:Noto Sans SC、PingFang SC(中文)、Inter(英文) +- 等宽字体(API Key 显示):JetBrains Mono、Fira Code + +--- + +## 12. 验收标准 + +### 12.1 视觉还原度 +- [ ] 侧边栏渐变色与原型一致 +- [ ] 导航项 active 状态有左侧竖线指示器 +- [ ] 卡片阴影和圆角符合规范 +- [ ] 状态指示器(●/◯/⚠)颜色准确 +- [ ] 模态对话框背景 overlay 正确显示 + +### 12.2 交互流畅性 +- [ ] 卡片加载动画平滑(60fps) +- [ ] 对话框打开/关闭无闪烁 +- [ ] 按钮 hover/click 反应 < 100ms +- [ ] 下拉菜单展开流畅 + +### 12.3 功能完整性 +- [ ] 可添加新模型 +- [ ] 模型连接测试正常工作 +- [ ] 删除模型弹出确认框 +- [ ] 设为默认后,卡片状态正确更新 +- [ ] 收藏/取消收藏功能正常 + +### 12.4 表单验证 +- [ ] 未输入 API Key 时"保存"按钮禁用 +- [ ] 连接失败时显示错误信息 +- [ ] 测试成功后自动填充模型信息 + +### 12.5 跨平台一致性 +- [ ] Windows 和 macOS 上侧边栏宽度一致 +- [ ] 字体渲染清晰 +- [ ] 颜色在两平台显示一致 + +--- + +**文档版本**:v1.0 +**最后更新**:2025-11-12 +**维护者**:产品设计团队 + diff --git a/desktop/spec/ui-design-03-dashboard.md b/desktop/spec/ui-design-03-dashboard.md new file mode 100644 index 0000000..fb6c3aa --- /dev/null +++ b/desktop/spec/ui-design-03-dashboard.md @@ -0,0 +1,557 @@ +# UI 设计文档:仪表板 / 主页(概览与对话列表) + +> **原型图参考**:Image 3 - 欢迎回来!(Dashboard 主页) +> **对应功能**:攻略对象列表、对话统计、最近对话、新建对话 + +--- + +## 1. 整体布局 + +### 1.1 页面属性 +- **页面类型**:主窗口内容区域 +- **尺寸**:全屏适配,最小 1200px × 800px +- **背景**:浅白色 `#F5F7FA` +- **滚动**:内容区支持垂直滚动 + +### 1.2 整体结构(分两列) +``` +┌─────────────────────────────────────────────────────────────────┐ +│ [侧边栏] [主内容区] │ +│ │ +│ ◆ 总览 ✓ 欢迎回来! │ +│ ◆ 攻略对象 这是你的对话项目快照。 │ +│ ◆ 对话编辑器 │ +│ ◆ LLM 配置 ┌─────────────────────────────┐ │ +│ ◆ 设置 │ 攻略对象: 12 │ │ +│ │ 对话: 89 │ │ +│ │ 分享: 256 │ │ +│ │ 故事标记: 42 │ │ +│ [帮助] └─────────────────────────────┘ │ +│ [报告问题] │ +│ [登出] ┌─────────────────────────────────────────┐│ +│ │ 最近对话 [实时助手] ││ +│ │ "我"与攻略对象的聊天日志摘要。 ││ +│ │ [新对话] ││ +│ │ ││ +│ │ 第1章:命运的相遇 ││ +│ │ 优衣在樱花丛中与优司见面,命运的奇缘... ││ +│ │ 攻略对象: [Miyu] [Akira] [Hana] [Yuki] ││ +│ │ 最后编辑: 2天前 ││ +│ │ ││ +│ │ 咖啡馆的误会 ││ +│ │ 一个关键的冲突场景。她与男主在咖啡馆... ││ +│ │ 攻略对象: [Miyu] [Akira] ││ +│ │ 最后编辑: 5天前 ││ +│ │ ││ +│ │ 图书馆的告白 ││ +│ │ 一个感人的逆转,优衣在学校图书馆学习... ││ +│ │ 攻略对象: [Miyu] ││ +│ │ 最后编辑: 1周前 ││ +│ │ ││ +│ │ [+ 创建新对话] ││ +│ │ 从头开始一段新对话。 ││ +│ │ ││ +│ └─────────────────────────────────────────┘│ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. 侧边栏导航(同 LLM Config 页) + +### 2.1 导航结构 +- 与 LLM 配置页相同的侧边栏设计 +- 当前高亮项:`◆ 总览 ✓` + +### 2.2 样式参考 +- 宽度:280px +- 背景:品牌粉红渐变 +- 参考 `ui-design-02-llm-config.md` 第 2 节 + +--- + +## 3. 顶部欢迎区 + +### 3.1 欢迎标题 +``` +欢迎回来! +这是你的对话项目快照。 +``` + +- **位置**:主内容区顶部,内边距 40px +- **背景**:白色 `#FFFFFF`,底部 1px 边框 `#E5E7EB` +- **标题**: + - 文字:`欢迎回来!` + - 字体:32px,粗体 700,颜色 `#1F2937` + - 下边距:12px +- **副标题**: + - 文字:`这是你的对话项目快照。` + - 字体:15px,颜色 `#6B7280` + - 下边距:0px + +--- + +## 4. 统计卡片区域 + +### 4.1 卡片布局 +``` +┌──────────────────┬──────────────────┬──────────────────┬──────────────────┐ +│ 攻略对象 │ 对话 │ 分享 │ 故事标记 │ +│ 12 │ 89 │ 256 │ 42 │ +└──────────────────┴──────────────────┴──────────────────┴──────────────────┘ +``` + +- **布局**:4 列网格,`gap: 16px` +- **容器**:内边距 40px(同欢迎区) +- **背景**:`#F5F7FA`(页面背景) + +### 4.2 单个统计卡片 +- **宽度**:calc(25% - 12px)(4 列等宽) +- **背景**:白色 `#FFFFFF` +- **圆角**:12px +- **内边距**:24px 20px +- **边框**:1px solid `#E5E7EB` +- **阴影**:`0 1px 3px rgba(0, 0, 0, 0.1)` +- **Hover**: + - 阴影升高到 `0 4px 12px rgba(0, 0, 0, 0.15)` + - 过渡 200ms ease + - 指针变为 pointer(可点击查看详情) + +### 4.3 卡片内容 +- **标签**(上): + - 文字:`攻略对象`、`对话`、`分享`、`故事标记` + - 字体:13px,颜色 `#6B7280` + - 下边距:8px + +- **数字**(下): + - 数值:`12`、`89`、`256`、`42` + - 字体:36px,粗体 700,颜色 `#1F2937` + - 对齐:左对齐 + +### 4.4 响应式 +- **宽度 ≥ 1200px**:4 列 +- **宽度 1000px ~ 1199px**:3 列(最后一列换行) +- **宽度 768px ~ 999px**:2 列 +- **宽度 < 768px**:1 列 + +--- + +## 5. 最近对话区域 + +### 5.1 区域容器 +- **内边距**:40px +- **背景**:`#F5F7FA`(页面背景) + +### 5.2 区域头部(标题 + 操作按钮) +``` +最近对话 [实时助手] [新对话] +"我"与攻略对象的聊天日志摘要。 +``` + +- **布局**:Flex row,`justify-content: space-between` +- **左侧**: + - **标题**:`最近对话`,22px 粗体 700,颜色 `#1F2937` + - **副标题**:`"我"与攻略对象的聊天日志摘要。`,13px,颜色 `#6B7280`,下边距 16px + +- **右侧**(操作按钮): + - **[实时助手]** 按钮 + - 尺寸:高 36px,宽 120px + - 背景:品牌粉红 `#D91B5C` + - 文字:白色,13px 粗体 + - 圆角:8px + - Hover:背景 `#C2185B` + - 左侧图标:⏱(时钟符号) + + - **[新对话]** 按钮 + - 尺寸:高 36px,宽 120px + - 背景:品牌粉红 `#D91B5C` + - 文字:白色,13px 粗体 + - 圆角:8px + - Hover:背景 `#C2185B` + - 左侧图标:+ (加号) + +--- + +## 6. 对话卡片列表 + +### 6.1 卡片整体容器 +- **背景**:白色 `#FFFFFF` +- **圆角**:12px +- **边框**:1px solid `#E5E7EB` +- **溢出**:`overflow: hidden`(圆角裁剪) +- **最大宽度**:900px +- **阴影**:`0 1px 3px rgba(0, 0, 0, 0.1)`,hover 升高到 `0 4px 12px rgba(0, 0, 0, 0.15)` + +### 6.2 单条对话卡片 +``` +┌─────────────────────────────────────────────────────────────┐ +│ 第1章:命运的相遇 │ +│ 优衣在樱花丛中与优司见面,命运的奇缘... │ +│ 攻略对象: [Miyu] [Akira] [Hana] [Yuki] │ +│ 最后编辑: 2天前 │ +└─────────────────────────────────────────────────────────────┘ +``` + +**布局方式**:垂直堆叠,相邻卡片间有 1px 分隔线 `#E5E7EB` + +**样式规范**: +- **内边距**:20px(上下左右) +- **背景**: + - 默认:白色 + - Hover:极浅灰 `#F9FAFB` +- **过渡**:背景色 200ms ease +- **指针**:pointer(可点击进入对话详情) + +#### 卡片头部(标题) +- **文字**:`第1章:命运的相遇` +- **字体**:18px,粗体 600,颜色 `#1F2937` +- **下边距**:8px + +#### 卡片主体(描述) +- **文字**:`优衣在樱花丛中与优司见面,命运的奇缘...` +- **字体**:14px,颜色 `#6B7280` +- **行高**:1.6 +- **最大行数**:2 行,超出显示省略号 `...` +- **下边距**:12px + +#### 卡片底部(元数据) +- **布局**:Flex row,`justify-content: space-between, align-items: center` +- **左侧**: + - **攻略对象标签** + - 格式:`攻略对象: [Miyu] [Akira] [Hana] [Yuki]` + - 每个标签为 inline-pill: + - 背景:`rgba(217, 27, 92, 0.1)`(极淡粉) + - 文字:`#D91B5C`(品牌粉色),11px + - 内边距:4px 8px + - 圆角:12px + - 间距:4px + - 超过 3 个标签时,显示 `+{剩余数}` 标签,点击展开全部 + - 字体标签:"攻略对象:" 为 11px,颜色 `#9CA3AF`(灰色) + +- **右侧**: + - **最后编辑时间** + - 格式:`最后编辑: 2天前`(相对时间) + - 字体:12px,颜色 `#9CA3AF` + - Hover 时显示绝对时间(如 "2025-11-10 14:32")作为 tooltip + +#### 交互行为 +- **点击卡片**:进入对话详情页面(对应 Image 4) +- **右键菜单**(可选): + - 编辑对话名称 + - 删除对话 + - 复制对话链接 + - 导出对话(JSON 或 PDF) + +--- + +## 7. 空状态 + +### 7.1 无对话记录 +显示条件:用户未创建任何对话 + +``` +┌────────────────────────────────┐ +│ │ +│ [⊕ 大图标] │ +│ │ +│ 还没有对话 │ +│ │ +│ 创建第一个对话,开始攻略之旅。 │ +│ │ +│ [+ 创建新对话] │ +│ │ +└────────────────────────────────┘ +``` + +**样式**: +- **背景**:白色卡片,圆角 12px,边框 2px dashed `#E5E7EB` +- **内容对齐**:居中 +- **图标**:`⊕`,48px,颜色 `#D1D5DB` +- **标题**:`还没有对话`,20px 粗体 600,颜色 `#1F2937`,下边距 8px +- **描述**:14px,颜色 `#6B7280` +- **按钮**:`+ 创建新对话`,品牌粉红背景 + +--- + +## 8. 创建新对话模态框 + +### 8.1 触发方式 +点击 "[新对话]" 按钮或空状态中的 "[+ 创建新对话]" 按钮 + +### 8.2 对话框属性 +- **宽度**:550px +- **背景**:白色 +- **圆角**:12px +- **阴影**:`0 10px 40px rgba(0, 0, 0, 0.2)` + +### 8.3 对话框结构 +``` +┌────────────────────────────────────────────┐ +│ 创建新对话 [×] │ +├────────────────────────────────────────────┤ +│ 对话标题(选填): │ +│ ┌──────────────────────────────────────┐ │ +│ │ 例:第1章:命运的相遇 │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ 描述(选填): │ +│ ┌──────────────────────────────────────┐ │ +│ │ 例:主角与女主在樱花树下相遇... │ │ +│ │ │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ 涉及的攻略对象: │ +│ ◇ Miyu (暧昧) │ +│ ◇ Akira (朋友) │ +│ ◇ Hana (竞争对手) │ +│ ◇ Yuki (闺蜜) │ +│ │ +│ [创建] [取消] │ +└────────────────────────────────────────────┘ +``` + +### 8.4 字段定义 + +#### 对话标题输入 +- **标签**:`对话标题(选填):`,13px 粗体 600 +- **输入框**: + - 高 40px + - 背景 `#F9FAFB`,边框 1px `#D1D5DB` + - 圆角 6px + - Placeholder:`例:第1章:命运的相遇`,颜色 `#9CA3AF` + - Focus:边框 `#D91B5C`,阴影 `0 0 0 3px rgba(217, 27, 92, 0.1)` + +#### 描述输入 +- **标签**:`描述(选填):`,13px 粗体 600 +- **文本框**: + - 高 100px + - 背景 `#F9FAFB`,边框 1px `#D1D5DB` + - 圆角 6px + - Placeholder:`例:主角与女主在樱花树下相遇...` + - 可调整大小(右下角缩放手柄) + - Focus:边框 `#D91B5C` + +#### 攻略对象选择 +- **标签**:`涉及的攻略对象:`,13px 粗体 600,下边距 12px +- **对象列表**: + - 显示应用中已有的所有攻略对象 + - 每个对象为一个 checkbox + 标签 + - **Checkbox 样式**: + - 未选中:空心圆 `◇`,颜色 `#D1D5DB` + - 选中:实心圆 `◆`,颜色 `#D91B5C` + - **标签**: + - 文字:`Miyu (暧昧)` + - 字体:13px,颜色 `#374151` + - 左边距 8px(checkbox 后) + - **行间距**:12px + +### 8.5 按钮栏 +``` + [创建] [取消] +``` + +- **[创建]** 按钮 + - 背景:品牌粉红 `#D91B5C` + - 文字:白色,13px 粗体 + - 内边距:10px 32px + - 圆角:6px + - Hover:背景 `#C2185B` + - 禁用:标题为空时,opacity 0.5,pointer-events none + +- **[取消]** 按钮 + - 背景:透明,边框 1px `#D1D5DB` + - 文字:`#374151`,13px 粗体 + - 内边距:10px 32px + - 圆角:6px + - Hover:背景 `#F3F4F6` + +--- + +## 9. 实时助手功能(侧板) + +### 9.1 侧板触发 +点击 "[实时助手]" 按钮时,从右侧滑出一个侧板 + +### 9.2 侧板属性 +- **宽度**:350px +- **位置**:固定定位,右侧,从 `translateX(100%)` 滑入到 `translateX(0)` +- **高度**:100vh +- **背景**:白色 `#FFFFFF`,左边 1px 分隔线 `#E5E7EB` +- **z-index**:确保在主内容上方(但低于模态框) +- **动画**:300ms ease-out + +### 9.3 侧板内容(可选扩展功能,暂简化) +``` +┌────────────────────────────┐ +│ 实时助手 [×]│ +├────────────────────────────┤ +│ │ +│ [录制按钮] │ +│ │ +│ 对话建议: │ +│ ... │ +│ │ +└────────────────────────────┘ +``` + +- 暂定为简化版本,功能延后:保留 UI 框架,内容可扩展 + +--- + +## 10. 页面转换动画 + +### 10.1 卡片加载动画 +- 对话卡片从底部以 stagger 效果进入(300ms ease-out) +- 每张卡片间隔 50ms + +### 10.2 对话框打开/关闭 +- **打开**:`scale: 0.9, opacity: 0` 到 `scale: 1, opacity: 1`,200ms ease-out +- **关闭**:反向,200ms ease-in +- **背景 overlay**:`rgba(0, 0, 0, 0)` 到 `rgba(0, 0, 0, 0.5)`,同步过渡 + +--- + +## 11. 响应式设计 + +### 11.1 桌面版(≥ 1200px) +- 侧边栏固定 280px +- 统计卡片 4 列 +- 对话卡片最大宽度 900px + +### 11.2 平板版(768px ~ 1199px) +- 侧边栏可折叠(汉堡菜单) +- 统计卡片 2 列 +- 对话卡片宽度自适应 + +### 11.3 移动/小窗口版(< 768px) +- 侧边栏隐藏,导航改为顶部 +- 统计卡片 1 列 +- 对话卡片全宽 + +--- + +## 12. 键盘快捷键 & 无障碍 + +### 12.1 快捷键 +- **Ctrl+N**:打开"新对话"对话框 +- **Ctrl+K**:搜索对话 +- **Enter**:激活聚焦的卡片或按钮 +- **Escape**:关闭对话框或侧板 + +### 12.2 屏幕阅读器 +- 统计卡片有 `role="region"` 和 `aria-label` +- 对话卡片有 `role="button"` 或 `` 标签(可聚焦、可激活) +- 模态框有 `role="dialog"` 和 `aria-modal="true"` + +### 12.3 颜色对比度 +- 所有文字与背景对比度 ≥ 4.5:1 + +--- + +## 13. 技术实现提示 + +### 13.1 React 组件结构 +```jsx + + + + + + + {conversations.length > 0 ? ( + + ) : ( + + )} + + + + {showNewConversationModal && ( + + )} + + {showLiveAssistant && ( + + )} + +``` + +### 13.2 样式方案 +- **Tailwind CSS** + 自定义变量 +- **Framer Motion** 处理卡片和模态框动画 + +### 13.3 状态管理 +- 使用 **Zustand** 或 **Jotai** 管理对话列表 +- 使用 **React Query** 缓存对话统计数据 +- 模态框和侧板状态独立管理 + +--- + +## 14. 设计资源清单 + +### 14.1 图标 +- 时钟 ⏱、加号 +、×、菱形 ◇◆、大加号 ⊕、复选框 ☑ + +### 14.2 颜色变量表 +```css +:root { + --brand-primary: #D91B5C; + --brand-gradient: linear-gradient(135deg, #D91B5C 0%, #C2185B 100%); + + --bg-page: #F5F7FA; + --bg-card: #FFFFFF; + --bg-input: #F9FAFB; + --bg-sidebar: var(--brand-gradient); + + --text-primary: #1F2937; + --text-secondary: #6B7280; + --text-muted: #9CA3AF; + + --border-default: #E5E7EB; + --border-dark: #D1D5DB; + + --tag-bg: rgba(217, 27, 92, 0.1); + --tag-text: #D91B5C; +} +``` + +--- + +## 15. 验收标准 + +### 15.1 视觉还原度 +- [ ] 欢迎标题和副标题排版正确 +- [ ] 统计卡片网格布局和对齐准确 +- [ ] 对话卡片标题、描述、标签、时间排版清晰 +- [ ] 攻略对象标签颜色与原型一致 +- [ ] 空状态图标和文字居中 + +### 15.2 交互流畅性 +- [ ] 卡片加载 stagger 动画平滑(60fps) +- [ ] 对话框打开/关闭无闪烁 +- [ ] 按钮 hover/click 反应 < 100ms +- [ ] 侧板滑入/滑出流畅 + +### 15.3 功能完整性 +- [ ] 新建对话弹窗正常打开 +- [ ] 对话标题和描述输入正常 +- [ ] 攻略对象复选框可正常选中/取消 +- [ ] 创建对话后,卡片出现在列表顶部 +- [ ] 点击对话卡片进入详情页 +- [ ] 实时助手侧板可打开/关闭 + +### 15.4 跨平台一致性 +- [ ] Windows 和 macOS 侧边栏宽度一致 +- [ ] 统计卡片在两平台渲染一致 +- [ ] 字体清晰 + +--- + +**文档版本**:v1.0 +**最后更新**:2025-11-12 +**维护者**:产品设计团队 + diff --git a/desktop/spec/ui-design-04-conversation-detail.md b/desktop/spec/ui-design-04-conversation-detail.md new file mode 100644 index 0000000..026ecb5 --- /dev/null +++ b/desktop/spec/ui-design-04-conversation-detail.md @@ -0,0 +1,614 @@ +# UI 设计文档:对话详情 / 分析页面(AI 复盘与数据化反馈) + +> **原型图参考**:Image 4 - 与 Miyu 的对话(对话详情与分析) +> **对应功能**:对话内容展示、AI 分析、好感度曲线、复盘建议 + +--- + +## 1. 整体布局 + +### 1.1 页面属性 +- **页面类型**:主窗口内容区域 +- **尺寸**:全屏适配,最小 1200px × 800px +- **背景**:浅白色 `#F5F7FA` +- **布局**:三列设计(侧栏 + 中间对话 + 右侧分析) + +### 1.2 整体结构 +``` +┌──────────────────────────────────────────────────────────────────┐ +│ [侧边栏] [中间对话区] [右侧分析板] │ +│ │ +│ ◆ 总览 与 Miyu 的对话 ✓ [AI 分析与编辑] │ +│ ◆ 攻略对象 这是你的对话项目快照。 │ +│ ◆ 对话编辑器 [AI 洞察] │ +│ ◆ LLM 配置 ┌────────────────┐ ┌──────────────────┐ │ +│ ◆ 设置 │ [攻略对象] │ │ 你似乎平衡了 │ │ +│ │ [对话] │ │ 主动性与被动性 │ │ +│ │ [分享] │ │ 很好! │ │ +│ │ [故事标记] │ │ │ │ +│ └────────────────┘ │ [编辑消息] │ │ +│ [帮助] │ │ │ +│ [报告问题] ┌────────────────┐ │ [编辑消息] │ │ +│ [登出] │ Miyu: 呃,真的吗?│ │ │ │ +│ │ 不过这听起来...│ │ [暂无推荐] │ │ +│ │ 我当然愿意! │ │ │ │ +│ │ │ │ [进一步建议] │ │ +│ │ 我: 我当然愿意│ │ │ │ +│ │ 让我试试... │ │ │ │ +│ │ │ │ [AI 复盘] │ │ +│ │ Miyu: 听起来...│ │ 你做得很棒... │ │ +│ │ 对我很特别 │ │ │ │ +│ │ │ │ [查看完整分析] │ │ +│ │ [💭 思考中] │ │ │ │ +│ │ [+ 添加消息] │ │ │ │ +│ └────────────────┘ └──────────────────┘ +│ +└──────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. 侧边栏导航(同前) + +### 2.1 结构 +- 与 LLM Config、Dashboard 相同的侧边栏 +- 当前高亮:`◆ 对话编辑器` +- 参考 `ui-design-02-llm-config.md` 第 2 节 + +--- + +## 3. 顶部标题栏(中间列) + +### 3.1 标题栏布局 +``` +与 Miyu 的对话 ✓ [⏮ 上一个] [下一个 ⏭] [⋮ 更多] +这是你的对话项目快照。 +``` + +- **高度**:80px +- **背景**:白色 `#FFFFFF` +- **边框**:底部 1px solid `#E5E7EB` +- **内边距**:20px 40px +- **布局**:Flexbox 列方向,上方标题行,下方按钮/时间行 + +### 3.2 标题行 +- **布局**:Flex row,`justify-content: space-between, align-items: center` +- **左侧**: + - **标题文字**:`与 Miyu 的对话`,24px 粗体 700,颜色 `#1F2937` + - **状态指示**:✓ 绿色徽章,表示对话已完成/已保存 + - 字体:14px,颜色 `#10B981` + +- **右侧**(操作按钮): + - **[⏮ 上一个]**:跳转到上一条对话,禁用态 opacity 0.5 + - **[下一个 ⏭]**:跳转到下一条对话,禁用态 opacity 0.5 + - **[⋮ 更多]**:下拉菜单 + - 编辑对话标题 + - 编辑对话描述 + - 删除对话 + - 导出对话(JSON/PDF) + - 查看历史版本 + - 按钮样式:高 36px,宽 100px,背景透明边框,圆角 6px,Hover 背景 `#F3F4F6` + +### 3.3 副标题行 +- **文字**:`这是你的对话项目快照。` +- **字体**:14px,颜色 `#6B7280` +- **右侧**(元数据): + - 创建时间:`创建于: 2025-11-10 14:32` + - 最后编辑:`最后编辑: 2小时前` + - 字体:12px,颜色 `#9CA3AF` + +--- + +## 4. 中间对话内容区 + +### 4.1 容器属性 +- **背景**:白色 `#FFFFFF` +- **宽度**:计算值 `calc(100% - 280px - 350px - 80px)`(侧栏 + 右板 + 间距) +- **高度**:`calc(100vh - 80px - 60px)`(标题栏 + 底部操作栏) +- **内边距**:20px(内部消息间距) +- **边框**:右侧 1px solid `#E5E7EB`(分隔右侧分析板) +- **滚动**:`overflow-y: auto`,自定义滚动条 + +### 4.2 对话气泡(同 Chat Window) +参考 `ui-design-01-chat-window.md` 第 3 节 + +**调整**: +- **消息时间戳**:每条消息下方显示,格式 `14:32` +- **消息分组**:按天分组,显示日期分隔线(如 "2025-11-10") +- **关键标记**: + - 重要消息(由 AI 标记):右上角 ⭐,深度灰色背景轻度强调 + - 转折点(好感度大幅变化):消息右侧显示好感度变化 `+20` 或 `-5` + +### 4.3 中间插入元素 + +#### AI 思考中指示器 +``` +[思考图标] AI 分析中... +``` +- 显示在对话列表中间 +- 高度 40px,背景 `rgba(59, 130, 246, 0.1)` +- 文字:`AI 分析中...`,13px,颜色蓝色 +- 脉冲动画 + +#### 添加消息按钮 +``` +[+ 添加消息] +``` +- 位置:对话列表底部 +- 样式:按钮样式,高 40px,背景 `rgba(217, 27, 92, 0.1)`,文字粉红色 +- 点击后打开消息编辑对话框 + +--- + +## 5. 底部操作栏(中间列) + +### 5.1 操作栏布局 +``` +[😊] [⏸] [✨] [⚙] [💾 保存] +``` + +- **高度**:64px +- **背景**:白色 `#FFFFFF`,上方 1px 分隔线 +- **内边距**:16px +- **布局**:Flexbox 横向,`justify-content: center, gap: 24px` +- **右侧**:"保存"按钮靠右对齐 + +### 5.2 按钮定义 +- **[😊]、[⏸]、[✨]、[⚙]**:同 Chat Window +- **[💾 保存]**: + - 背景:品牌粉红 `#D91B5C` + - 文字:白色,13px 粗体 + - 内边距:10px 24px + - 圆角:6px + - Hover:背景 `#C2185B` + - 点击后短暂变为 "✓ 已保存",1 秒后恢复 + +--- + +## 6. 右侧分析板 + +### 6.1 板块容器 +- **宽度**:350px(固定) +- **背景**:`#F9FAFB`(极浅灰) +- **高度**:100vh +- **内边距**:20px +- **滚动**:`overflow-y: auto` +- **边框**:左侧 1px solid `#E5E7EB` + +### 6.2 标题栏 +``` +AI 分析与编辑 +``` +- **文字**:`AI 分析与编辑` +- **字体**:16px 粗体 600,颜色 `#1F2937` +- **下边距**:20px +- **下方有 1px 分隔线** `#E5E7EB` + +### 6.3 分析卡片(多个) + +#### 卡片容器 +``` +┌─────────────────────────────┐ +│ 你似乎平衡了主动性与被动性 │ +│ 很好! │ +│ │ +│ [编辑消息] │ +└─────────────────────────────┘ +``` + +**样式**: +- **背景**:白色 `#FFFFFF` +- **圆角**:8px +- **边框**:1px solid `#E5E7EB` +- **内边距**:16px +- **间距**:卡片之间 12px +- **阴影**:`0 1px 2px rgba(0, 0, 0, 0.05)` +- **Hover**:阴影升高到 `0 2px 4px rgba(0, 0, 0, 0.1)` + +#### 卡片内容 +- **分析文本**:14px,行高 1.6,颜色 `#374151` +- **icon**(可选):卡片左上角小图标(如 💭、💡、⚠ 等),尺寸 20px + +#### 编辑按钮 +- **位置**:卡片底部,右对齐 +- **文字**:`[编辑消息]` +- **样式**: + - 背景:蓝色 `#3B82F6` + - 文字:白色,12px 粗体 + - 内边距:6px 12px + - 圆角:4px + - Hover:背景 `#2563EB` + +### 6.4 分析卡片类型与显示规则 + +| 类型 | 图标 | 样本文本 | 触发条件 | +|------|------|------|------| +| 正面反馈 | 💭 | 你似乎平衡了主动性与被动性,很好! | AI 判定消息质量高 | +| 中立观察 | 💡 | 暂无推荐 | 无特殊建议或中性回应 | +| 改进建议 | ⚠️ | 在对方提到"最近工作有点累"时,你转移了话题。建议:多关心对方状态。 | AI 检测到可改进点 | +| 关键转折 | 🎯 | 进一步建议:下次对话中提及这个话题。 | 消息引发好感度大幅变化 | + +### 6.5 AI 复盘分析 + +位置:分析卡片集合底部 + +``` +┌──────────────────────────────────┐ +│ AI 复盘 │ +│ ────────────────────────────────│ +│ 你做得很棒,这次对话中你... │ +│ ...(省略部分内容)... │ +│ ...保持了自然和真诚。 │ +│ │ +│ [查看完整分析] → │ +└──────────────────────────────────┘ +``` + +**样式**: +- **背景**:品牌粉色极淡版 `rgba(217, 27, 92, 0.05)` +- **圆角**:8px +- **边框**:1px solid `rgba(217, 27, 92, 0.2)` +- **内边距**:16px +- **标题**:`AI 复盘`,14px 粗体 600,颜色 `#D91B5C` +- **下方分隔线**:1px `rgba(217, 27, 92, 0.1)` +- **摘要文本**: + - 字体:13px,行高 1.6,颜色 `#374151` + - 最多显示 3 行,超出显示省略号 +- **[查看完整分析] →**: + - 链接样式,文字 `#D91B5C`,13px + - Hover:下划线 + - 点击打开全屏分析页面(或模态框) + +--- + +## 7. 好感度曲线(对话详情页变体) + +### 7.1 触发方式 +右侧分析板有一个 "📊 好感度变化" 折叠卡片(可选显示) + +``` +┌──────────────────────────┐ +│ 📊 好感度变化 ▼ │ ← 点击展开/折叠 +├──────────────────────────┤ +│ ┌────────────────────┐ │ +│ │ [曲线图] │ │ +│ │ 50 ─────────────→ │ │ +│ │ ↗ ↗ │ │ +│ │ 70 │ │ +│ │ (当前分数) │ │ +│ └────────────────────┘ │ +│ 本次对话好感度变化: │ +│ 初始: 50 → 最终: 70 │ +│ 变化: +20 │ +└──────────────────────────┘ +``` + +**样式**: +- **折叠头**:背景 `#F3F4F6`,高 44px,圆角 8px(仅顶部),内边距 12px +- **展开内容**:背景白色,圆角 8px(仅底部),内边距 16px +- **曲线图表**: + - 使用简单的 SVG 或 Canvas 绘制 + - x 轴:对话阶段(消息编号) + - y 轴:好感度分数(0-100) + - 线条颜色:渐变 `#3B82F6` → `#D91B5C` + - 圆点标记关键转折点 + - 宽度:100%,高度 150px + +### 7.2 统计数据展示 +- **初始好感度**:50 +- **最终好感度**:70 +- **总变化**:+20 +- **高峰**:72(第 8 条消息后) +- **低谷**:48(第 3 条消息后) + +--- + +## 8. 消息编辑模态框 + +### 8.1 触发方式 +- 点击"[编辑消息]"按钮 +- 或在中间对话区右键点击消息气泡选择"编辑" + +### 8.2 对话框属性 +- **宽度**:600px +- **背景**:白色 +- **圆角**:12px +- **阴影**:`0 10px 40px rgba(0, 0, 0, 0.2)` + +### 8.3 对话框结构 +``` +┌────────────────────────────────────────────┐ +│ 编辑消息 [×] │ +├────────────────────────────────────────────┤ +│ 角色: │ +│ ◎ Miyu ◎ 我 (Player) │ +│ │ +│ 消息内容: │ +│ ┌──────────────────────────────────────┐ │ +│ │ 听起来真不错!也可以这么说,我听说...│ │ +│ │ │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ 标记为关键:☐ 此消息为攻略关键转折点 │ +│ │ +│ [保存修改] [取消] │ +└────────────────────────────────────────────┘ +``` + +### 8.4 字段定义 + +#### 角色选择 +- **格式**:单选按钮(Radio) +- **选项**: + - ◎ Miyu(对方) + - ◎ 我 (Player)(用户自己) +- **默认**:根据被编辑消息的角色自动选中 +- **不可改变对方为用户**(防止数据混乱) + +#### 消息内容 +- **标签**:`消息内容:`,13px 粗体 +- **文本框**: + - 高 120px + - 背景 `#F9FAFB`,边框 1px `#D1D5DB` + - 圆角 6px + - 可调整大小 + - Focus:边框 `#D91B5C` + +#### 关键标记复选框 +- **样式**:标准 checkbox + 标签 +- **标签**:`此消息为攻略关键转折点`,13px,颜色 `#374151` +- **选中时**:右侧显示小星标 ⭐ +- **影响**:选中后,该消息在对话区会显示星标,在分析中优先级提高 + +### 8.5 按钮栏 +- **[保存修改]**:品牌粉红,保存后关闭对话框 +- **[取消]**:透明边框,关闭不保存 + +--- + +## 9. 全屏分析页面(可选扩展) + +### 9.1 触发方式 +点击右侧分析板中的 "[查看完整分析] →" + +### 9.2 页面内容 +``` +[返回] 完整 AI 分析报告 [导出为 PDF] + +===================================================== + +对话分析报告 + +对话标题:与 Miyu 的对话 +对话日期:2025-11-10 14:32 +分析时间:2025-11-10 15:45 + +───────────────────────────────────────────────── + +做得很好的地方 ✓ +• 及时回应对方邀约,表现出兴趣,效果:好感度 +15 +• 主动提及共同话题(旅游和咖啡),拉近距离,效果:好感度 +8 +• ... + +可以改进的地方 ⚠ +• 在对方提到"最近工作有点累"时,你转移了话题。 + 建议:多关心对方状态,增进情感连接。 +• 回应长度偏短,缺乏深度。 + 建议:加入 1-2 个细节或真诚的感受。 + +下次对话建议 💡 +1. 主动询问对方的工作情况(因为之前她提过压力) +2. 分享你最近的经历,建立相互理解 +3. 邀请对方参加共同感兴趣的活动 + +═════════════════════════════════════════════════ + +好感度变化详解 + +初始好感度:50 +最终好感度:70 +总变化:+20 + +过程分析: +• 第 1-3 条消息:好感度 50 → 55(缓慢上升) + 原因:互动开放,但缺乏特殊吸引力 +• 第 4-6 条消息:好感度 55 → 70(快速上升) + 原因:话题契合度高,双方共鸣增加 +• ... + +═════════════════════════════════════════════════ + +[导出为 PDF] [返回] +``` + +**样式**: +- 单列布局,宽度 900px,居中 +- 背景:白色 +- 排版:清晰分段,标题粗体 18px,正文 14px +- 导出按钮:右上固定或底部居中 + +--- + +## 10. 交互动画 + +### 10.1 右侧分析板入场 +- 从右侧 `translateX(100%)` 滑入到 `translateX(0)`,300ms ease-out + +### 10.2 分析卡片加载 +- Stagger 效果,每张卡片延迟 50ms +- 从下方 `translateY(20px), opacity: 0` 到 `translateY(0), opacity: 1` + +### 10.3 好感度曲线动画 +- 折叠卡片展开时,曲线图从 0% 宽度动画到 100%,500ms ease-out + +--- + +## 11. 响应式设计 + +### 11.1 桌面版(≥ 1400px) +- 三列布局:侧栏 280px + 中间 flex + 右板 350px +- 对话内容全量显示 + +### 11.2 平板版(1000px ~ 1399px) +- 三列压缩:侧栏可折叠,右板宽度减少到 300px + +### 11.3 小屏版(< 1000px) +- 右侧分析板改为上滑模态框(Sheet Modal) +- 对话内容占据大部分宽度 + +--- + +## 12. 键盘快捷键 & 无障碍 + +### 12.1 快捷键 +- **Ctrl+S**:保存对话 +- **Ctrl+E**:编辑当前消息 +- **Ctrl+Z**:撤销编辑 +- **Escape**:关闭编辑对话框或关闭分析板 + +### 12.2 屏幕阅读器 +- 消息气泡有 `role="article"` 和 `aria-label`(含消息主体) +- 分析卡片有 `role="region"` 和 `aria-labelledby` +- 按钮有 `aria-label` 和 `aria-pressed`(切换类按钮) + +### 12.3 颜色对比度 +- 所有文字与背景对比度 ≥ 4.5:1 + +--- + +## 13. 技术实现提示 + +### 13.1 React 组件结构 +```jsx + + + + + + + + + + + + + + + + {showEditMessageModal && ( + + )} + + {showFullAnalysis && ( + + )} + +``` + +### 13.2 数据结构 +```typescript +interface Message { + id: string; + role: 'user' | 'partner'; + content: string; + timestamp: Date; + isKeyTurningPoint: boolean; + sentimentImpact: number; // -20 to +20 +} + +interface Analysis { + type: 'positive' | 'neutral' | 'improvement' | 'turning_point'; + text: string; + messageId?: string; + icon: string; +} + +interface ConversationDetail { + id: string; + title: string; + partnerId: string; + messages: Message[]; + analyses: Analysis[]; + aiReview: string; + sentimentCurve: number[]; // 好感度序列 +} +``` + +### 13.3 样式方案 +- **Tailwind CSS** + 自定义变量 +- **Recharts** 或 **Chart.js** 绘制好感度曲线 + +### 13.4 状态管理 +- 使用 **Zustand** 或 **Jotai** 管理对话内容和编辑状态 +- 使用 **React Query** 缓存分析数据 + +--- + +## 14. 设计资源清单 + +### 14.1 图标 +- 返回 ⏮、前进 ⏭、更多 ⋮、保存 💾、思考 💭、灯泡 💡、警告 ⚠、目标 🎯、图表 📊、导出 📄 + +### 14.2 颜色变量表 +```css +:root { + --brand-primary: #D91B5C; + --brand-light: rgba(217, 27, 92, 0.05); + + --bg-page: #F5F7FA; + --bg-card: #FFFFFF; + --bg-input: #F9FAFB; + --bg-chart: #F9FAFB; + + --text-primary: #1F2937; + --text-secondary: #6B7280; + --text-muted: #9CA3AF; + + --border-default: #E5E7EB; + + --status-positive: #10B981; + --status-neutral: #6B7280; + --status-improvement: #F59E0B; + --status-turning: #D91B5C; +} +``` + +--- + +## 15. 验收标准 + +### 15.1 视觉还原度 +- [ ] 标题栏与副标题排版正确 +- [ ] 消息气泡格式(含时间、星标、好感度变化)完整 +- [ ] 右侧分析卡片样式一致 +- [ ] 好感度曲线图表清晰 +- [ ] 编辑消息对话框表单正确 + +### 15.2 交互流畅性 +- [ ] 右侧分析板滑入/滑出流畅 +- [ ] 分析卡片加载 stagger 动画平滑 +- [ ] 曲线图展开动画自然 +- [ ] 消息编辑对话框打开/关闭无闪烁 + +### 15.3 功能完整性 +- [ ] 消息可编辑,修改后实时显示 +- [ ] 关键标记可正确设置/取消 +- [ ] 分析卡片内容准确反映对话内容 +- [ ] AI 复盘分析文字清晰 +- [ ] 查看完整分析可正确打开全屏页面 + +### 15.4 跨平台一致性 +- [ ] Windows 和 macOS 三列布局对齐 +- [ ] 字体渲染清晰 +- [ ] 图表在两平台显示一致 + +--- + +**文档版本**:v1.0 +**最后更新**:2025-11-12 +**维护者**:产品设计团队 + diff --git a/desktop/spec/ui-design-components.md b/desktop/spec/ui-design-components.md new file mode 100644 index 0000000..5a4251b --- /dev/null +++ b/desktop/spec/ui-design-components.md @@ -0,0 +1,1001 @@ +# UI 组件库设计文档(Reusable Components) + +> **用途**:跨页面复用的基础组件、容器组件和业务组件规范 +> **框架**:React + Tailwind CSS + Framer Motion +> **设计工具**:Figma(组件库系统) + +--- + +## 1. 设计系统基础 + +### 1.1 设计令牌(Design Tokens) + +#### 颜色系统 +```css +/* 品牌色 */ +--color-brand-primary: #D91B5C; /* 品牌粉红 */ +--color-brand-primary-dark: #C2185B; +--color-brand-primary-light: rgba(217, 27, 92, 0.1); + +/* 中性色 */ +--color-gray-50: #F9FAFB; +--color-gray-100: #F3F4F6; +--color-gray-200: #E5E7EB; +--color-gray-300: #D1D5DB; +--color-gray-400: #9CA3AF; +--color-gray-500: #6B7280; +--color-gray-600: #4B5563; +--color-gray-700: #374151; +--color-gray-800: #1F2937; +--color-gray-900: #111827; + +/* 语义色 */ +--color-success: #10B981; +--color-warning: #F59E0B; +--color-error: #EF4444; +--color-info: #3B82F6; + +/* 深色模式补充 */ +--color-dark-bg: #1F1F1F; +--color-dark-surface: #2A2A2A; +--color-dark-text: #E5E7EB; +``` + +#### 尺寸系统 +```css +/* 间距 */ +--spacing-xs: 4px; +--spacing-sm: 8px; +--spacing-md: 16px; +--spacing-lg: 24px; +--spacing-xl: 32px; +--spacing-2xl: 40px; + +/* 圆角 */ +--radius-xs: 4px; +--radius-sm: 6px; +--radius-md: 8px; +--radius-lg: 12px; +--radius-xl: 16px; +--radius-full: 9999px; + +/* 阴影 */ +--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05); +--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); +--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); +--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.15); +--shadow-xl: 0 10px 40px rgba(0, 0, 0, 0.2); + +/* 字体 */ +--font-family-sans: 'Noto Sans SC', 'PingFang SC', Inter, sans-serif; +--font-family-mono: 'JetBrains Mono', 'Fira Code', monospace; + +/* 字体尺寸 */ +--font-size-xs: 11px; +--font-size-sm: 12px; +--font-size-base: 13px; +--font-size-md: 14px; +--font-size-lg: 15px; +--font-size-xl: 16px; +--font-size-2xl: 18px; +--font-size-3xl: 20px; +--font-size-4xl: 24px; +--font-size-5xl: 28px; +--font-size-6xl: 32px; +--font-size-7xl: 36px; + +/* 行高 */ +--line-height-tight: 1.2; +--line-height-normal: 1.5; +--line-height-relaxed: 1.6; +--line-height-loose: 1.8; +``` + +#### 动画曲线 +```css +--ease-in: ease-in; +--ease-out: ease-out; +--ease-in-out: ease-in-out; +--ease-linear: linear; +--ease-custom-spring: cubic-bezier(0.25, 0.46, 0.45, 0.94); +``` + +--- + +## 2. 基础组件库(Atomic Components) + +### 2.1 Button 组件 + +#### 属性定义 +```tsx +interface ButtonProps { + variant?: 'primary' | 'secondary' | 'outline' | 'ghost'; + size?: 'xs' | 'sm' | 'md' | 'lg'; + disabled?: boolean; + loading?: boolean; + fullWidth?: boolean; + children: React.ReactNode; + onClick?: () => void; + className?: string; +} +``` + +#### 样式规范 + +| Variant | 背景 | 文字色 | 边框 | Hover 背景 | +|---------|------|-------|------|-----------| +| primary | `#D91B5C` | 白色 | 无 | `#C2185B` | +| secondary | `#F3F4F6` | `#374151` | `#E5E7EB` | `#E5E7EB` | +| outline | 透明 | `#374151` | `#D1D5DB` | `#F9FAFB` | +| ghost | 透明 | `#374151` | 无 | 无色 | + +| Size | 高度 | 内边距 | 字体 | 圆角 | +|------|------|-------|------|------| +| xs | 28px | 6px 12px | 11px | 4px | +| sm | 32px | 8px 16px | 12px | 6px | +| md | 40px | 10px 20px | 14px | 8px | +| lg | 48px | 12px 24px | 16px | 8px | + +#### 实现示例 +```tsx +export const Button: React.FC = ({ + variant = 'primary', + size = 'md', + disabled = false, + loading = false, + fullWidth = false, + children, + onClick, + className, +}) => { + const variantClasses = { + primary: 'bg-brand-primary text-white hover:bg-brand-primary-dark', + secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200 border border-gray-200', + outline: 'bg-transparent text-gray-800 border border-gray-300 hover:bg-gray-50', + ghost: 'bg-transparent text-gray-800 hover:bg-gray-100', + }; + + const sizeClasses = { + xs: 'h-7 px-3 text-xs', + sm: 'h-8 px-4 text-sm', + md: 'h-10 px-5 text-base', + lg: 'h-12 px-6 text-lg', + }; + + return ( + + ); +}; +``` + +--- + +### 2.2 Input 组件 + +#### 属性定义 +```tsx +interface InputProps { + type?: 'text' | 'email' | 'password' | 'number' | 'search'; + placeholder?: string; + value?: string; + onChange?: (value: string) => void; + disabled?: boolean; + error?: string; + icon?: React.ReactNode; + size?: 'sm' | 'md' | 'lg'; + className?: string; +} +``` + +#### 样式规范 +- **背景**:`#F9FAFB` +- **边框**:`1px solid #D1D5DB` +- **Focus**:边框 `#D91B5C`,阴影 `0 0 0 3px rgba(217, 27, 92, 0.1)` +- **圆角**:6px +- **高度**:40px(md) +- **内边距**:10px 12px +- **字体**:14px,颜色 `#374151` +- **Placeholder**:14px,颜色 `#9CA3AF` +- **Error 状态**:边框变为红色 `#EF4444`,下方显示错误文本 + +#### 实现示例 +```tsx +export const Input: React.FC = ({ + type = 'text', + placeholder, + value, + onChange, + disabled, + error, + icon, + size = 'md', + className, +}) => { + return ( +
+ onChange?.(e.target.value)} + placeholder={placeholder} + disabled={disabled} + className={clsx( + 'w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded-md', + 'focus:outline-none focus:border-brand-primary focus:ring-2 focus:ring-brand-light', + 'placeholder-gray-400 text-gray-800', + 'transition-all duration-200', + error && 'border-error focus:border-error', + disabled && 'opacity-50 cursor-not-allowed', + icon && 'pl-9', + className, + )} + /> + {icon && {icon}} + {error && ( + {error} + )} +
+ ); +}; +``` + +--- + +### 2.3 Card 组件 + +#### 属性定义 +```tsx +interface CardProps { + children: React.ReactNode; + variant?: 'default' | 'elevated' | 'outlined'; + padding?: 'none' | 'sm' | 'md' | 'lg'; + className?: string; + onClick?: () => void; +} +``` + +#### 样式规范 +- **背景**:白色 `#FFFFFF` +- **边框**:`1px solid #E5E7EB`(outlined 变体) +- **圆角**:12px +- **阴影**: + - default: `0 1px 3px rgba(0, 0, 0, 0.1)` + - elevated: `0 4px 12px rgba(0, 0, 0, 0.15)` + - outlined: 无 +- **过渡**:hover 时阴影升高 + +--- + +### 2.4 Badge 组件 + +#### 属性定义 +```tsx +interface BadgeProps { + children: React.ReactNode; + variant?: 'primary' | 'success' | 'warning' | 'error' | 'info'; + size?: 'sm' | 'md'; + icon?: React.ReactNode; +} +``` + +#### 样式规范 +``` +variant: primary + 背景: rgba(217, 27, 92, 0.1) + 文字: #D91B5C + +variant: success + 背景: rgba(16, 185, 129, 0.1) + 文字: #10B981 +``` + +--- + +### 2.5 Tag 组件 + +#### 属性定义 +```tsx +interface TagProps { + label: string; + color?: string; + onRemove?: () => void; + interactive?: boolean; +} +``` + +#### 样式规范 +- **背景**:`rgba(217, 27, 92, 0.1)`(默认) +- **文字**:`#D91B5C` +- **内边距**:4px 8px +- **圆角**:12px +- **字体**:11px 粗体 +- **可删除**:右侧显示 `×` 按钮 + +--- + +### 2.6 Modal 组件 + +#### 属性定义 +```tsx +interface ModalProps { + isOpen: boolean; + title?: string; + children: React.ReactNode; + onClose: () => void; + size?: 'sm' | 'md' | 'lg'; + showCloseButton?: boolean; +} +``` + +#### 样式规范 +- **背景 Overlay**:`rgba(0, 0, 0, 0.5)` +- **对话框**:白色卡片,圆角 12px,阴影 xl +- **尺寸**: + - sm: 400px + - md: 600px + - lg: 800px +- **动画**:`scale(0.9, 0) → scale(1, 1)`,200ms ease-out + +--- + +### 2.7 Spinner 组件 + +#### 属性定义 +```tsx +interface SpinnerProps { + size?: 'sm' | 'md' | 'lg'; + color?: string; +} +``` + +#### 样式规范 +- **尺寸**:16px (sm), 24px (md), 32px (lg) +- **颜色**:`#D91B5C`(默认) +- **动画**:旋转 360°,2s 无限循环 + +--- + +## 3. 容器组件(Layout Components) + +### 3.1 Layout 组件(主框架) + +```tsx +interface LayoutProps { + children: React.ReactNode; + showSidebar?: boolean; +} + +export const Layout: React.FC = ({ children, showSidebar = true }) => { + return ( +
+ {showSidebar && } +
+ {children} +
+
+ ); +}; +``` + +### 3.2 Sidebar 组件 + +```tsx +interface SidebarProps { + activeTab?: string; + onTabChange?: (tab: string) => void; +} + +export const Sidebar: React.FC = ({ activeTab, onTabChange }) => { + // 导航项列表 + const navItems = [ + { id: 'overview', label: '总览', icon: '◆' }, + { id: 'targets', label: '攻略对象', icon: '◆' }, + { id: 'editor', label: '对话编辑器', icon: '◆' }, + { id: 'llm', label: 'LLM 配置', icon: '◆' }, + { id: 'settings', label: '设置', icon: '◆' }, + ]; + + return ( + + ); +}; +``` + +### 3.3 Header 组件 + +```tsx +interface HeaderProps { + title: string; + subtitle?: string; + actions?: React.ReactNode; +} + +export const Header: React.FC = ({ title, subtitle, actions }) => { + return ( +
+
+
+

{title}

+ {subtitle &&

{subtitle}

} +
+ {actions &&
{actions}
} +
+
+ ); +}; +``` + +--- + +## 4. 业务组件(Feature Components) + +### 4.1 MessageBubble 组件 + +```tsx +interface MessageBubbleProps { + content: string; + role: 'user' | 'partner'; + timestamp?: Date; + isKeyPoint?: boolean; + sentimentImpact?: number; + onEdit?: () => void; +} + +export const MessageBubble: React.FC = ({ + content, + role, + timestamp, + isKeyPoint, + sentimentImpact, + onEdit, +}) => { + const isUser = role === 'user'; + + return ( +
+
+

{content}

+ {timestamp && ( + + {timestamp.toLocaleTimeString()} + + )} +
+ + {isKeyPoint && } + {sentimentImpact && ( + 0 ? 'text-success' : 'text-error')}> + {sentimentImpact > 0 ? '+' : ''}{sentimentImpact} + + )} +
+ ); +}; +``` + +### 4.2 SuggestionCard 组件 + +```tsx +interface SuggestionCardProps { + suggestion: string; + tags: string[]; + expectedImpact?: number; + onSelect?: () => void; + onDismiss?: () => void; +} + +export const SuggestionCard: React.FC = ({ + suggestion, + tags, + expectedImpact, + onSelect, + onDismiss, +}) => { + return ( + +

{suggestion}

+
+
+ {tags.map(tag => ( + {tag} + ))} +
+ {expectedImpact && ( + ❤️ +{expectedImpact} + )} +
+
+ + +
+
+ ); +}; +``` + +### 4.3 StatCard 组件 + +```tsx +interface StatCardProps { + label: string; + value: number | string; + icon?: React.ReactNode; + trend?: 'up' | 'down'; +} + +export const StatCard: React.FC = ({ + label, + value, + icon, + trend, +}) => { + return ( + +
+
+

{label}

+

{value}

+
+ {icon && {icon}} +
+ {trend && ( +

+ {trend === 'up' ? '↑' : '↓'} 较上周 +

+ )} +
+ ); +}; +``` + +### 4.4 ConversationCard 组件 + +```tsx +interface ConversationCardProps { + title: string; + description: string; + partners: string[]; + lastEdited: Date; + onClick?: () => void; +} + +export const ConversationCard: React.FC = ({ + title, + description, + partners, + lastEdited, + onClick, +}) => { + return ( + +

{title}

+

{description}

+ +
+
+ {partners.map(partner => ( + + ))} +
+ + {formatRelativeTime(lastEdited)} + +
+
+ ); +}; +``` + +--- + +## 5. 组件状态管理模式 + +### 5.1 使用 Zustand +```tsx +import create from 'zustand'; + +interface UIStore { + sidebarOpen: boolean; + toggleSidebar: () => void; + + selectedConversation: string | null; + setSelectedConversation: (id: string | null) => void; + + showModal: boolean; + setShowModal: (show: boolean) => void; +} + +export const useUIStore = create((set) => ({ + sidebarOpen: true, + toggleSidebar: () => set(state => ({ sidebarOpen: !state.sidebarOpen })), + + selectedConversation: null, + setSelectedConversation: (id) => set({ selectedConversation: id }), + + showModal: false, + setShowModal: (show) => set({ showModal: show }), +})); +``` + +--- + +## 6. 动画和过渡库 + +### 6.1 使用 Framer Motion +```tsx +import { motion, AnimatePresence } from 'framer-motion'; + +/* 淡入淡出动画 */ +export const fadeInOut = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, + transition: { duration: 0.2 }, +}; + +/* 从下滑入 */ +export const slideUp = { + initial: { y: 20, opacity: 0 }, + animate: { y: 0, opacity: 1 }, + exit: { y: 20, opacity: 0 }, + transition: { duration: 0.3, ease: 'easeOut' }, +}; + +/* 从右滑入 */ +export const slideInRight = { + initial: { x: 300, opacity: 0 }, + animate: { x: 0, opacity: 1 }, + exit: { x: 300, opacity: 0 }, + transition: { duration: 0.3, ease: 'easeOut' }, +}; + +/* 缩放 + 淡入 */ +export const scaleIn = { + initial: { scale: 0.9, opacity: 0 }, + animate: { scale: 1, opacity: 1 }, + exit: { scale: 0.9, opacity: 0 }, + transition: { duration: 0.2, ease: 'easeOut' }, +}; + +/* Stagger 容器 */ +export const container = { + hidden: { opacity: 0 }, + show: { + opacity: 1, + transition: { + staggerChildren: 0.05, + }, + }, +}; + +/* Stagger 子项 */ +export const item = { + hidden: { opacity: 0, y: 20 }, + show: { + opacity: 1, + y: 0, + transition: { duration: 0.3 }, + }, +}; +``` + +--- + +## 7. 类型定义(TypeScript) + +### 7.1 共享类型 +```typescript +/* 通用类型 */ +export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +export type Variant = 'primary' | 'secondary' | 'outline' | 'ghost'; +export type Status = 'idle' | 'loading' | 'success' | 'error'; + +/* 用户相关 */ +export interface User { + id: string; + name: string; + avatar?: string; +} + +/* 对话相关 */ +export interface Conversation { + id: string; + title: string; + description?: string; + partnerId: string; + messages: Message[]; + createdAt: Date; + updatedAt: Date; +} + +export interface Message { + id: string; + role: 'user' | 'partner'; + content: string; + timestamp: Date; + isKeyPoint?: boolean; + sentimentImpact?: number; +} + +/* 分析相关 */ +export interface Analysis { + id: string; + type: 'positive' | 'neutral' | 'improvement' | 'turning_point'; + text: string; + messageId?: string; +} + +export interface SentimentCurve { + points: number[]; + startValue: number; + endValue: number; + maxValue: number; + minValue: number; +} +``` + +--- + +## 8. 主题切换(深色/浅色模式) + +### 8.1 主题上下文 +```tsx +import React, { createContext, useContext, useState } from 'react'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [theme, setTheme] = useState('dark'); + + const toggleTheme = () => { + setTheme(t => t === 'light' ? 'dark' : 'light'); + // 同时更新 HTML 属性和 localStorage + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('theme', theme); + }; + + return ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) throw new Error('useTheme must be used within ThemeProvider'); + return context; +}; +``` + +### 8.2 Tailwind 深色模式配置 +```js +// tailwind.config.js +module.exports = { + darkMode: 'class', + theme: { + colors: { + /* ... */ + }, + }, +}; +``` + +--- + +## 9. 测试覆盖(Jest + React Testing Library) + +### 9.1 按钮组件测试示例 +```typescript +import { render, screen, fireEvent } from '@testing-library/react'; +import { Button } from './Button'; + +describe('Button Component', () => { + it('renders button with correct text', () => { + render(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('applies primary variant styles', () => { + render(); + const button = screen.getByText('Submit'); + expect(button).toHaveClass('bg-brand-primary', 'text-white'); + }); + + it('handles click events', () => { + const handleClick = jest.fn(); + render(); + + fireEvent.click(screen.getByText('Click')); + expect(handleClick).toHaveBeenCalled(); + }); + + it('disables button when disabled prop is true', () => { + render(); + expect(screen.getByText('Disabled')).toBeDisabled(); + }); +}); +``` + +--- + +## 10. 性能优化 + +### 10.1 使用 React.memo 避免不必要重渲染 +```tsx +export const MessageBubble = React.memo((props: MessageBubbleProps) => { + // ... +}); +``` + +### 10.2 使用 useCallback 缓存函数 +```tsx +const handleSave = useCallback(() => { + saveConversation(conversationId); +}, [conversationId]); +``` + +### 10.3 列表虚拟化(大数据量) +```tsx +import { FixedSizeList as List } from 'react-window'; + +const MessageList = ({ messages }: { messages: Message[] }) => { + const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => ( +
+ +
+ ); + + return ( + + {Row} + + ); +}; +``` + +--- + +## 11. 无障碍(Accessibility) + +### 11.1 ARIA 属性 +```tsx + + + +``` + +### 11.2 键盘导航 +```tsx +const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick?.(); + } + if (e.key === 'Escape') { + onClose?.(); + } +}; +``` + +--- + +## 12. 文档与示例(Storybook) + +### 12.1 按钮 Story +```tsx +import { Button } from './Button'; + +export default { + title: 'Components/Button', + component: Button, +}; + +export const Primary = () => ; +export const Secondary = () => ; +export const Disabled = () => ; +export const Loading = () => ; +``` + +--- + +## 13. 组件清单 + +| 组件名 | 类型 | 用途 | 依赖 | +|-------|------|------|------| +| Button | 基础 | 通用按钮 | - | +| Input | 基础 | 文本输入 | - | +| Card | 基础 | 卡片容器 | - | +| Badge | 基础 | 徽章标签 | - | +| Tag | 基础 | 可删除标签 | - | +| Modal | 基础 | 模态对话框 | Framer Motion | +| Spinner | 基础 | 加载指示器 | Framer Motion | +| Layout | 容器 | 主框架 | Sidebar, Header | +| Sidebar | 容器 | 侧边栏导航 | Button | +| Header | 容器 | 页面头部 | Button | +| MessageBubble | 业务 | 聊天气泡 | Card | +| SuggestionCard | 业务 | 建议卡片 | Card, Badge, Button | +| StatCard | 业务 | 统计卡片 | Card | +| ConversationCard | 业务 | 对话卡片 | Card, Tag | + +--- + +## 14. 版本管理与更新流程 + +### 14.1 Changelog 示例 +``` +## v1.0.0 (2025-11-15) + +### 新增 +- Button 组件:支持 4 种变体、4 种尺寸 +- Input 组件:支持错误状态和图标 +- Modal 组件:完整的模态对话框实现 + +### 改进 +- 优化 MessageBubble 动画性能 +- 改进 Tag 组件可访问性 + +### 修复 +- 修复 Button loading 状态在移动端的显示问题 +``` + +--- + +**文档版本**:v1.0 +**最后更新**:2025-11-12 +**维护者**:前端架构团队 +**设计工具**:Figma (组件库链接) +**代码仓库**:`/src/components` +**Storybook**:http://localhost:6006 + diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png deleted file mode 120000 index 9d12755..0000000 --- a/fastlane/metadata/android/en-US/images/icon.png +++ /dev/null @@ -1 +0,0 @@ -../../../../../app/src/main/ic_launcher-playstore.png \ No newline at end of file diff --git a/.kotlin/errors/errors-1759038853800.log b/mobile/.kotlin/errors/errors-1759038853800.log similarity index 100% rename from .kotlin/errors/errors-1759038853800.log rename to mobile/.kotlin/errors/errors-1759038853800.log diff --git a/LICENSE b/mobile/LICENSE similarity index 100% rename from LICENSE rename to mobile/LICENSE diff --git a/README.md b/mobile/README.md similarity index 100% rename from README.md rename to mobile/README.md diff --git a/app/.gitignore b/mobile/app/.gitignore similarity index 100% rename from app/.gitignore rename to mobile/app/.gitignore diff --git a/app/build.gradle.kts b/mobile/app/build.gradle.kts similarity index 100% rename from app/build.gradle.kts rename to mobile/app/build.gradle.kts diff --git a/app/proguard-rules.pro b/mobile/app/proguard-rules.pro similarity index 100% rename from app/proguard-rules.pro rename to mobile/app/proguard-rules.pro diff --git a/app/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt b/mobile/app/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt similarity index 100% rename from app/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt rename to mobile/app/src/androidTest/java/com/example/livegalgame/ExampleInstrumentedTest.kt diff --git a/app/src/main/AndroidManifest.xml b/mobile/app/src/main/AndroidManifest.xml similarity index 100% rename from app/src/main/AndroidManifest.xml rename to mobile/app/src/main/AndroidManifest.xml diff --git a/app/src/main/assets/Ah.mp3 b/mobile/app/src/main/assets/Ah.mp3 similarity index 100% rename from app/src/main/assets/Ah.mp3 rename to mobile/app/src/main/assets/Ah.mp3 diff --git a/app/src/main/assets/TeaBreak.mp3 b/mobile/app/src/main/assets/TeaBreak.mp3 similarity index 100% rename from app/src/main/assets/TeaBreak.mp3 rename to mobile/app/src/main/assets/TeaBreak.mp3 diff --git a/app/src/main/assets/bgm.mp3 b/mobile/app/src/main/assets/bgm.mp3 similarity index 100% rename from app/src/main/assets/bgm.mp3 rename to mobile/app/src/main/assets/bgm.mp3 diff --git a/app/src/main/assets/casual.mp3 b/mobile/app/src/main/assets/casual.mp3 similarity index 100% rename from app/src/main/assets/casual.mp3 rename to mobile/app/src/main/assets/casual.mp3 diff --git a/app/src/main/assets/heart.svg b/mobile/app/src/main/assets/heart.svg similarity index 100% rename from app/src/main/assets/heart.svg rename to mobile/app/src/main/assets/heart.svg diff --git a/app/src/main/assets/image2.svg b/mobile/app/src/main/assets/image2.svg similarity index 100% rename from app/src/main/assets/image2.svg rename to mobile/app/src/main/assets/image2.svg diff --git a/app/src/main/assets/image3.svg b/mobile/app/src/main/assets/image3.svg similarity index 100% rename from app/src/main/assets/image3.svg rename to mobile/app/src/main/assets/image3.svg diff --git a/app/src/main/assets/image4.svg b/mobile/app/src/main/assets/image4.svg similarity index 100% rename from app/src/main/assets/image4.svg rename to mobile/app/src/main/assets/image4.svg diff --git a/app/src/main/assets/image4.xml b/mobile/app/src/main/assets/image4.xml similarity index 100% rename from app/src/main/assets/image4.xml rename to mobile/app/src/main/assets/image4.xml diff --git a/app/src/main/assets/image5.svg b/mobile/app/src/main/assets/image5.svg similarity index 100% rename from app/src/main/assets/image5.svg rename to mobile/app/src/main/assets/image5.svg diff --git a/app/src/main/assets/image7.svg b/mobile/app/src/main/assets/image7.svg similarity index 100% rename from app/src/main/assets/image7.svg rename to mobile/app/src/main/assets/image7.svg diff --git a/app/src/main/assets/image8.svg b/mobile/app/src/main/assets/image8.svg similarity index 100% rename from app/src/main/assets/image8.svg rename to mobile/app/src/main/assets/image8.svg diff --git a/app/src/main/assets/image9.svg b/mobile/app/src/main/assets/image9.svg similarity index 100% rename from app/src/main/assets/image9.svg rename to mobile/app/src/main/assets/image9.svg diff --git a/app/src/main/assets/model/README b/mobile/app/src/main/assets/model/README similarity index 100% rename from app/src/main/assets/model/README rename to mobile/app/src/main/assets/model/README diff --git a/app/src/main/assets/model/am/final.mdl b/mobile/app/src/main/assets/model/am/final.mdl similarity index 100% rename from app/src/main/assets/model/am/final.mdl rename to mobile/app/src/main/assets/model/am/final.mdl diff --git a/app/src/main/assets/model/conf/mfcc.conf b/mobile/app/src/main/assets/model/conf/mfcc.conf similarity index 100% rename from app/src/main/assets/model/conf/mfcc.conf rename to mobile/app/src/main/assets/model/conf/mfcc.conf diff --git a/app/src/main/assets/model/conf/model.conf b/mobile/app/src/main/assets/model/conf/model.conf similarity index 100% rename from app/src/main/assets/model/conf/model.conf rename to mobile/app/src/main/assets/model/conf/model.conf diff --git a/app/src/main/assets/model/graph/Gr.fst b/mobile/app/src/main/assets/model/graph/Gr.fst similarity index 100% rename from app/src/main/assets/model/graph/Gr.fst rename to mobile/app/src/main/assets/model/graph/Gr.fst diff --git a/app/src/main/assets/model/graph/HCLr.fst b/mobile/app/src/main/assets/model/graph/HCLr.fst similarity index 100% rename from app/src/main/assets/model/graph/HCLr.fst rename to mobile/app/src/main/assets/model/graph/HCLr.fst diff --git a/app/src/main/assets/model/graph/disambig_tid.int b/mobile/app/src/main/assets/model/graph/disambig_tid.int similarity index 100% rename from app/src/main/assets/model/graph/disambig_tid.int rename to mobile/app/src/main/assets/model/graph/disambig_tid.int diff --git a/app/src/main/assets/model/graph/phones/word_boundary.int b/mobile/app/src/main/assets/model/graph/phones/word_boundary.int similarity index 100% rename from app/src/main/assets/model/graph/phones/word_boundary.int rename to mobile/app/src/main/assets/model/graph/phones/word_boundary.int diff --git a/app/src/main/assets/model/ivector/final.dubm b/mobile/app/src/main/assets/model/ivector/final.dubm similarity index 100% rename from app/src/main/assets/model/ivector/final.dubm rename to mobile/app/src/main/assets/model/ivector/final.dubm diff --git a/app/src/main/assets/model/ivector/final.ie b/mobile/app/src/main/assets/model/ivector/final.ie similarity index 100% rename from app/src/main/assets/model/ivector/final.ie rename to mobile/app/src/main/assets/model/ivector/final.ie diff --git a/app/src/main/assets/model/ivector/final.mat b/mobile/app/src/main/assets/model/ivector/final.mat similarity index 100% rename from app/src/main/assets/model/ivector/final.mat rename to mobile/app/src/main/assets/model/ivector/final.mat diff --git a/app/src/main/assets/model/ivector/global_cmvn.stats b/mobile/app/src/main/assets/model/ivector/global_cmvn.stats similarity index 100% rename from app/src/main/assets/model/ivector/global_cmvn.stats rename to mobile/app/src/main/assets/model/ivector/global_cmvn.stats diff --git a/app/src/main/assets/model/ivector/online_cmvn.conf b/mobile/app/src/main/assets/model/ivector/online_cmvn.conf similarity index 100% rename from app/src/main/assets/model/ivector/online_cmvn.conf rename to mobile/app/src/main/assets/model/ivector/online_cmvn.conf diff --git a/app/src/main/assets/model/ivector/splice.conf b/mobile/app/src/main/assets/model/ivector/splice.conf similarity index 100% rename from app/src/main/assets/model/ivector/splice.conf rename to mobile/app/src/main/assets/model/ivector/splice.conf diff --git a/app/src/main/assets/model/uuid b/mobile/app/src/main/assets/model/uuid similarity index 100% rename from app/src/main/assets/model/uuid rename to mobile/app/src/main/assets/model/uuid diff --git a/app/src/main/ic_launcher-playstore.png b/mobile/app/src/main/ic_launcher-playstore.png similarity index 100% rename from app/src/main/ic_launcher-playstore.png rename to mobile/app/src/main/ic_launcher-playstore.png diff --git a/app/src/main/java/com/jstone/livegalgame/MainActivity.kt b/mobile/app/src/main/java/com/jstone/livegalgame/MainActivity.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/MainActivity.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/MainActivity.kt diff --git a/app/src/main/java/com/jstone/livegalgame/Utils.kt b/mobile/app/src/main/java/com/jstone/livegalgame/Utils.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/Utils.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/Utils.kt diff --git a/app/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt b/mobile/app/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/dialog/TriggerManagementDialog.kt diff --git a/app/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt b/mobile/app/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/dialog/keywordDialog.kt diff --git a/app/src/main/java/com/jstone/livegalgame/model/DialogType.kt b/mobile/app/src/main/java/com/jstone/livegalgame/model/DialogType.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/model/DialogType.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/model/DialogType.kt diff --git a/app/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt b/mobile/app/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/model/KeywordTrigger.kt diff --git a/app/src/main/java/com/jstone/livegalgame/model/Trigger.kt b/mobile/app/src/main/java/com/jstone/livegalgame/model/Trigger.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/model/Trigger.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/model/Trigger.kt diff --git a/app/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt b/mobile/app/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/speech/KeywordSpeechListener.kt diff --git a/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt b/mobile/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/ui/CameraScreen.kt diff --git a/app/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt b/mobile/app/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/ui/theme/Color.kt diff --git a/app/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt b/mobile/app/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/ui/theme/Theme.kt diff --git a/app/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt b/mobile/app/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt similarity index 100% rename from app/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt rename to mobile/app/src/main/java/com/jstone/livegalgame/ui/theme/Type.kt diff --git a/app/src/main/res/AppIcons.zip b/mobile/app/src/main/res/AppIcons.zip similarity index 100% rename from app/src/main/res/AppIcons.zip rename to mobile/app/src/main/res/AppIcons.zip diff --git a/app/src/main/res/drawable/chapter.png b/mobile/app/src/main/res/drawable/chapter.png similarity index 100% rename from app/src/main/res/drawable/chapter.png rename to mobile/app/src/main/res/drawable/chapter.png diff --git a/app/src/main/res/drawable/chocake.png b/mobile/app/src/main/res/drawable/chocake.png similarity index 100% rename from app/src/main/res/drawable/chocake.png rename to mobile/app/src/main/res/drawable/chocake.png diff --git a/app/src/main/res/drawable/heart.xml b/mobile/app/src/main/res/drawable/heart.xml similarity index 100% rename from app/src/main/res/drawable/heart.xml rename to mobile/app/src/main/res/drawable/heart.xml diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/mobile/app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_background.xml rename to mobile/app/src/main/res/drawable/ic_launcher_background.xml diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/mobile/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_foreground.xml rename to mobile/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/main/res/drawable/image2.xml b/mobile/app/src/main/res/drawable/image2.xml similarity index 100% rename from app/src/main/res/drawable/image2.xml rename to mobile/app/src/main/res/drawable/image2.xml diff --git a/app/src/main/res/drawable/image3.xml b/mobile/app/src/main/res/drawable/image3.xml similarity index 100% rename from app/src/main/res/drawable/image3.xml rename to mobile/app/src/main/res/drawable/image3.xml diff --git a/app/src/main/res/drawable/image4.xml b/mobile/app/src/main/res/drawable/image4.xml similarity index 100% rename from app/src/main/res/drawable/image4.xml rename to mobile/app/src/main/res/drawable/image4.xml diff --git a/app/src/main/res/drawable/image5.xml b/mobile/app/src/main/res/drawable/image5.xml similarity index 100% rename from app/src/main/res/drawable/image5.xml rename to mobile/app/src/main/res/drawable/image5.xml diff --git a/app/src/main/res/drawable/image7.xml b/mobile/app/src/main/res/drawable/image7.xml similarity index 100% rename from app/src/main/res/drawable/image7.xml rename to mobile/app/src/main/res/drawable/image7.xml diff --git a/app/src/main/res/drawable/image8.xml b/mobile/app/src/main/res/drawable/image8.xml similarity index 100% rename from app/src/main/res/drawable/image8.xml rename to mobile/app/src/main/res/drawable/image8.xml diff --git a/app/src/main/res/drawable/image9.xml b/mobile/app/src/main/res/drawable/image9.xml similarity index 100% rename from app/src/main/res/drawable/image9.xml rename to mobile/app/src/main/res/drawable/image9.xml diff --git a/app/src/main/res/drawable/lemon.png b/mobile/app/src/main/res/drawable/lemon.png similarity index 100% rename from app/src/main/res/drawable/lemon.png rename to mobile/app/src/main/res/drawable/lemon.png diff --git a/app/src/main/res/drawable/strbcake.png b/mobile/app/src/main/res/drawable/strbcake.png similarity index 100% rename from app/src/main/res/drawable/strbcake.png rename to mobile/app/src/main/res/drawable/strbcake.png diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/mobile/app/src/main/res/mipmap-hdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher.webp rename to mobile/app/src/main/res/mipmap-hdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/mobile/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp rename to mobile/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/mobile/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher_round.webp rename to mobile/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/mobile/app/src/main/res/mipmap-mdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher.webp rename to mobile/app/src/main/res/mipmap-mdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/mobile/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp rename to mobile/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/mobile/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher_round.webp rename to mobile/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/mobile/app/src/main/res/mipmap-xhdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher.webp rename to mobile/app/src/main/res/mipmap-xhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/mobile/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp rename to mobile/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/mobile/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp rename to mobile/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher.webp rename to mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp rename to mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp rename to mobile/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp rename to mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp rename to mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp rename to mobile/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/values/colors.xml b/mobile/app/src/main/res/values/colors.xml similarity index 100% rename from app/src/main/res/values/colors.xml rename to mobile/app/src/main/res/values/colors.xml diff --git a/app/src/main/res/values/ic_launcher_background.xml b/mobile/app/src/main/res/values/ic_launcher_background.xml similarity index 100% rename from app/src/main/res/values/ic_launcher_background.xml rename to mobile/app/src/main/res/values/ic_launcher_background.xml diff --git a/app/src/main/res/values/strings.xml b/mobile/app/src/main/res/values/strings.xml similarity index 100% rename from app/src/main/res/values/strings.xml rename to mobile/app/src/main/res/values/strings.xml diff --git a/app/src/main/res/values/themes.xml b/mobile/app/src/main/res/values/themes.xml similarity index 100% rename from app/src/main/res/values/themes.xml rename to mobile/app/src/main/res/values/themes.xml diff --git a/app/src/main/res/xml/backup_rules.xml b/mobile/app/src/main/res/xml/backup_rules.xml similarity index 100% rename from app/src/main/res/xml/backup_rules.xml rename to mobile/app/src/main/res/xml/backup_rules.xml diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/mobile/app/src/main/res/xml/data_extraction_rules.xml similarity index 100% rename from app/src/main/res/xml/data_extraction_rules.xml rename to mobile/app/src/main/res/xml/data_extraction_rules.xml diff --git a/app/src/test/java/com/example/livegalgame/ExampleUnitTest.kt b/mobile/app/src/test/java/com/example/livegalgame/ExampleUnitTest.kt similarity index 100% rename from app/src/test/java/com/example/livegalgame/ExampleUnitTest.kt rename to mobile/app/src/test/java/com/example/livegalgame/ExampleUnitTest.kt diff --git a/build.gradle.kts b/mobile/build.gradle.kts similarity index 100% rename from build.gradle.kts rename to mobile/build.gradle.kts diff --git a/fastlane/metadata/android/en-US/full_description.txt b/mobile/fastlane/metadata/android/en-US/full_description.txt similarity index 100% rename from fastlane/metadata/android/en-US/full_description.txt rename to mobile/fastlane/metadata/android/en-US/full_description.txt diff --git a/mobile/fastlane/metadata/android/en-US/images/icon.png b/mobile/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 0000000..9d12755 --- /dev/null +++ b/mobile/fastlane/metadata/android/en-US/images/icon.png @@ -0,0 +1 @@ +../../../../../app/src/main/ic_launcher-playstore.png \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/short_description.txt b/mobile/fastlane/metadata/android/en-US/short_description.txt similarity index 100% rename from fastlane/metadata/android/en-US/short_description.txt rename to mobile/fastlane/metadata/android/en-US/short_description.txt diff --git a/fastlane/metadata/android/en-US/title.txt b/mobile/fastlane/metadata/android/en-US/title.txt similarity index 100% rename from fastlane/metadata/android/en-US/title.txt rename to mobile/fastlane/metadata/android/en-US/title.txt diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/mobile/fastlane/metadata/android/zh-CN/full_description.txt similarity index 100% rename from fastlane/metadata/android/zh-CN/full_description.txt rename to mobile/fastlane/metadata/android/zh-CN/full_description.txt diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/mobile/fastlane/metadata/android/zh-CN/short_description.txt similarity index 100% rename from fastlane/metadata/android/zh-CN/short_description.txt rename to mobile/fastlane/metadata/android/zh-CN/short_description.txt diff --git a/fastlane/metadata/android/zh-CN/title.txt b/mobile/fastlane/metadata/android/zh-CN/title.txt similarity index 100% rename from fastlane/metadata/android/zh-CN/title.txt rename to mobile/fastlane/metadata/android/zh-CN/title.txt diff --git a/gradle.properties b/mobile/gradle.properties similarity index 100% rename from gradle.properties rename to mobile/gradle.properties diff --git a/gradle/libs.versions.toml b/mobile/gradle/libs.versions.toml similarity index 100% rename from gradle/libs.versions.toml rename to mobile/gradle/libs.versions.toml diff --git a/gradle/wrapper/gradle-wrapper.jar b/mobile/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to mobile/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/mobile/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/wrapper/gradle-wrapper.properties rename to mobile/gradle/wrapper/gradle-wrapper.properties diff --git a/gradlew b/mobile/gradlew similarity index 100% rename from gradlew rename to mobile/gradlew diff --git a/gradlew.bat b/mobile/gradlew.bat similarity index 100% rename from gradlew.bat rename to mobile/gradlew.bat diff --git "a/release/\345\220\216\347\273\255apk\345\234\250tags\344\270\255\345\217\221\345\270\203.txt" "b/mobile/release/\345\220\216\347\273\255apk\345\234\250tags\344\270\255\345\217\221\345\270\203.txt" similarity index 100% rename from "release/\345\220\216\347\273\255apk\345\234\250tags\344\270\255\345\217\221\345\270\203.txt" rename to "mobile/release/\345\220\216\347\273\255apk\345\234\250tags\344\270\255\345\217\221\345\270\203.txt" diff --git a/settings.gradle.kts b/mobile/settings.gradle.kts similarity index 100% rename from settings.gradle.kts rename to mobile/settings.gradle.kts From 4d176b529929b94b5c485c76ed86af77c40901e4 Mon Sep 17 00:00:00 2001 From: chenspeculation Date: Wed, 12 Nov 2025 22:11:07 +0800 Subject: [PATCH 025/147] Remove unused .idea configuration files and add initial mobile app structure with essential files, including Gradle setup, AndroidManifest, and UI components. Also, include metadata for app description in both English and Chinese. --- .../70433c63813d57abeb67ff85363cbc0c.jpg | Bin 0 -> 95173 bytes .../70753aea0271777b0d92bfa4fb3ad015.jpg | Bin 0 -> 71497 bytes .../9fc9fa48e8988b3eb496f07c45b7a6b6.jpg | Bin 0 -> 111185 bytes .../cfef12820f837d316c204595222899b9.jpg | Bin 0 -> 119624 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 desktop/spec/images/70433c63813d57abeb67ff85363cbc0c.jpg create mode 100644 desktop/spec/images/70753aea0271777b0d92bfa4fb3ad015.jpg create mode 100644 desktop/spec/images/9fc9fa48e8988b3eb496f07c45b7a6b6.jpg create mode 100644 desktop/spec/images/cfef12820f837d316c204595222899b9.jpg diff --git a/desktop/spec/images/70433c63813d57abeb67ff85363cbc0c.jpg b/desktop/spec/images/70433c63813d57abeb67ff85363cbc0c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1242fcbb4e2d26f97916210941cfcec32705f0a2 GIT binary patch literal 95173 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
yc|0~ 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 L=Psd}#drS?r}L7w literal 0 HcmV?d00001 From 1a911cb7c7527820be7501e9a79d997d7bf102a2 Mon Sep 17 00:00:00 2001 From: chenspeculation Date: Thu, 13 Nov 2025 00:14:43 +0800 Subject: [PATCH 026/147] feat: add initial desktop application structure and development documentation - Introduced core files for the LiveGalGame desktop application, including main process and preload scripts. - Added essential HTML files for the main interface and HUD. - Created development plan and progress summary documentation. - Updated .gitignore to exclude unnecessary files. - Established package.json for dependency management and build scripts. --- .gitignore | 3 +- desktop/DEVELOPER_GUIDE.md | 1322 ------- desktop/DEVELOPMENT_PLAN.md | 441 +++ desktop/PROMPT_SUMMARY.md | 151 + desktop/README.md | 89 +- desktop/package.json | 45 + desktop/pnpm-lock.yaml | 3079 +++++++++++++++++ desktop/pnpm-workspace.yaml | 2 + desktop/src/main.js | 196 ++ desktop/src/preload.js | 28 + desktop/src/renderer/conversation-editor.html | 283 ++ desktop/src/renderer/hud.html | 253 ++ desktop/src/renderer/index.html | 254 ++ desktop/src/renderer/js/renderer.js | 405 +++ 14 files changed, 5222 insertions(+), 1329 deletions(-) delete mode 100644 desktop/DEVELOPER_GUIDE.md create mode 100644 desktop/DEVELOPMENT_PLAN.md create mode 100644 desktop/PROMPT_SUMMARY.md create mode 100644 desktop/package.json create mode 100644 desktop/pnpm-lock.yaml create mode 100644 desktop/pnpm-workspace.yaml create mode 100644 desktop/src/main.js create mode 100644 desktop/src/preload.js create mode 100644 desktop/src/renderer/conversation-editor.html create mode 100644 desktop/src/renderer/hud.html create mode 100644 desktop/src/renderer/index.html create mode 100644 desktop/src/renderer/js/renderer.js diff --git a/.gitignore b/.gitignore index c7be6c8..2627a50 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.iml .gradle -/local.properties +/local. +/desktop/node_modules .DS_Store /build /captures diff --git a/desktop/DEVELOPER_GUIDE.md b/desktop/DEVELOPER_GUIDE.md deleted file mode 100644 index cd74b2a..0000000 --- a/desktop/DEVELOPER_GUIDE.md +++ /dev/null @@ -1,1322 +0,0 @@ -# 🚀 LiveGalGame 桌面版开发者快速启动指南 - -> **目标**:从零开始搭建 Electron 应用,完整实现所有功能 -> **预计时间**:4-6 周(取决于团队规模和并行度) -> **技术栈**:Electron + React + TypeScript + Tailwind CSS + SQLite - ---- - -## 📍 项目阶段概览 - -``` -第一阶段(第 1 周):项目初始化与环境搭建 - ↓ -第二阶段(第 1-2 周):组件库与基础设施 - ↓ -第三阶段(第 2-3 周):核心功能开发(音频、LLM、数据) - ↓ -第四阶段(第 3-4 周):页面与业务逻辑集成 - ↓ -第五阶段(第 4-5 周):打包、测试与优化 - ↓ -第六阶段(第 5-6 周):发版与迭代 -``` - ---- - -# 第一阶段:项目初始化与环境搭建(第 1 周) - -## 1.1 环境检查清单 - -在开始前,确保已安装: - -```bash -# 检查 Node.js 版本(建议 18.x+) -node --version # 应为 v18.0.0 或更高 - -# 检查 npm 版本(建议 9.x+) -npm --version - -# 检查 Git -git --version -``` - -如未安装,请访问: -- Node.js:https://nodejs.org -- Git:https://git-scm.com - -## 1.2 初始化项目结构 - -### 第一步:克隆或创建项目 - -```bash -cd ~/LiveGalGame -mkdir desktop -cd desktop - -# 初始化 npm 项目 -npm init -y -``` - -### 第二步:安装核心依赖 - -```bash -npm install \ - electron \ - react \ - react-dom \ - typescript \ - tailwindcss \ - postcss \ - autoprefixer \ - framer-motion \ - zustand \ - @react-query/core \ - axios -``` - -### 第三步:安装开发依赖 - -```bash -npm install -D \ - @types/react \ - @types/react-dom \ - @types/node \ - ts-loader \ - webpack \ - webpack-cli \ - webpack-dev-server \ - html-webpack-plugin \ - @testing-library/react \ - @testing-library/jest-dom \ - jest \ - @storybook/react \ - @storybook/addon-essentials \ - electron-builder \ - cross-env -``` - -### 第四步:创建基础目录结构 - -```bash -# 从项目根目录执行 -mkdir -p src/{main,renderer,components,pages,hooks,store,types,utils,assets} -mkdir -p public -mkdir -p spec -mkdir -p tests -mkdir -p .github/workflows -``` - -最终结构: - -``` -desktop/ -├── src/ -│ ├── main/ -│ │ ├── index.ts # Electron 主进程入口 -│ │ ├── preload.ts # 预加载脚本(安全桥接) -│ │ └── ipc/ # IPC 通信处理器 -│ │ -│ ├── renderer/ -│ │ ├── index.tsx # React 入口 -│ │ └── App.tsx # 根组件 -│ │ -│ ├── components/ -│ │ ├── base/ # 基础组件(Button, Input 等) -│ │ ├── containers/ # 容器组件(Layout, Sidebar 等) -│ │ └── features/ # 业务组件(MessageBubble 等) -│ │ -│ ├── pages/ -│ │ ├── ChatWindow.tsx # 对话 HUD 浮窗 -│ │ ├── Dashboard.tsx # 主页 -│ │ ├── LLMConfig.tsx # LLM 配置 -│ │ └── ConversationDetail.tsx # 对话详情 -│ │ -│ ├── hooks/ -│ │ ├── useTheme.ts # 主题 hook -│ │ ├── useUIStore.ts # UI 状态 hook -│ │ └── useConversation.ts # 对话数据 hook -│ │ -│ ├── store/ -│ │ ├── ui.ts # UI 状态(Zustand) -│ │ ├── conversation.ts # 对话状态 -│ │ └── config.ts # 应用配置 -│ │ -│ ├── types/ -│ │ ├── index.ts # 全局类型定义 -│ │ ├── conversation.ts # 对话相关类型 -│ │ └── llm.ts # LLM 相关类型 -│ │ -│ ├── utils/ -│ │ ├── logger.ts # 日志工具 -│ │ ├── storage.ts # 本地存储 -│ │ ├── api.ts # API 请求 -│ │ └── formatters.ts # 格式化工具 -│ │ -│ ├── assets/ -│ │ ├── icons/ -│ │ ├── fonts/ -│ │ └── styles/ -│ │ ├── globals.css # 全局样式 -│ │ ├── tailwind.css # Tailwind 入口 -│ │ └── animations.css # 自定义动画 -│ │ -│ └── main.css # 应用主样式 -│ -├── public/ -│ ├── index.html # 渲染进程 HTML -│ └── preload.js # 预加载脚本分发 -│ -├── spec/ -│ ├── prd-desktop.md # ← 产品需求文档 -│ ├── tech-architecture.md # ← 技术架构 -│ ├── ui-design-01-chat-window.md # ← UI 设计 1 -│ ├── ui-design-02-llm-config.md # ← UI 设计 2 -│ ├── ui-design-03-dashboard.md # ← UI 设计 3 -│ ├── ui-design-04-conversation-detail.md # ← UI 设计 4 -│ ├── ui-design-components.md # ← 组件库规范 -│ ├── audio-capture-tech-note.md # ← 音频采集技术 -│ ├── llm-integration.md # ← LLM 集成 -│ ├── hud-ux.md # ← HUD 交互细节 -│ ├── data-model.md # ← 数据模型 -│ ├── build-and-release.md # ← 构建发版 -│ ├── privacy-and-permissions.md # ← 隐私权限 -│ └── test-plan.md # ← 测试计划 -│ -├── tests/ -│ ├── unit/ -│ ├── integration/ -│ └── e2e/ -│ -├── .github/ -│ └── workflows/ -│ ├── build.yml -│ └── release.yml -│ -├── package.json -├── tsconfig.json # TypeScript 配置 -├── webpack.config.js # Webpack 配置 -├── tailwind.config.js # Tailwind 配置 -├── postcss.config.js # PostCSS 配置 -├── jest.config.js # Jest 配置 -├── electron-builder.yml # 打包配置 -├── README.md -└── DEVELOPER_GUIDE.md # ← 本文档 -``` - -## 1.3 配置文件创建 - -### 1.3.1 tsconfig.json - -```json -{ - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "jsx": "react-jsx", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "paths": { - "@/*": ["src/*"], - "@components/*": ["src/components/*"], - "@pages/*": ["src/pages/*"], - "@hooks/*": ["src/hooks/*"], - "@store/*": ["src/store/*"], - "@types/*": ["src/types/*"], - "@utils/*": ["src/utils/*"] - } - }, - "include": ["src"], - "exclude": ["node_modules", "dist", "tests"] -} -``` - -### 1.3.2 tailwind.config.js - -```javascript -module.exports = { - content: ["./src/**/*.{js,jsx,ts,tsx}"], - theme: { - extend: { - colors: { - brand: { - primary: "#D91B5C", - dark: "#C2185B", - light: "rgba(217, 27, 92, 0.1)", - }, - }, - spacing: { - xs: "4px", - sm: "8px", - md: "16px", - lg: "24px", - xl: "32px", - "2xl": "40px", - }, - borderRadius: { - xs: "4px", - sm: "6px", - md: "8px", - lg: "12px", - xl: "16px", - }, - }, - }, - plugins: [], -}; -``` - -### 1.3.3 package.json scripts - -```json -{ - "scripts": { - "start": "cross-env NODE_ENV=development electron .", - "dev": "concurrently \"npm run webpack:dev\" \"wait-on http://localhost:8080 && npm run start\"", - "webpack:dev": "webpack serve --config webpack.config.js --mode development", - "build": "webpack --config webpack.config.js --mode production", - "test": "jest", - "test:watch": "jest --watch", - "storybook": "storybook dev -p 6006", - "storybook:build": "storybook build", - "lint": "eslint src", - "package": "electron-builder", - "package:win": "electron-builder --win", - "package:mac": "electron-builder --mac", - "release": "npm run build && electron-builder -p always" - } -} -``` - -## 1.4 验证环境 - -```bash -# 检查依赖安装成功 -npm list electron react typescript - -# 尝试编译 -npm run build - -# 应该看到 "build successful" 消息 -``` - -## 1.5 参考文档 - -| 文档 | 用途 | -|------|------| -| **README.md** | 项目概览和快速开始 | -| **tech-architecture.md** | 了解 Electron 主/渲染进程分层 | -| **build-and-release.md** | 理解打包流程(虽然现在还不需要) | - ---- - -# 第二阶段:组件库与基础设施(第 1-2 周) - -## 2.1 创建设计系统与主题 - -### 第一步:创建设计令牌文件 - -**文件**:`src/assets/styles/tokens.ts` - -```typescript -export const colors = { - // 品牌色 - brand: { - primary: "#D91B5C", - primaryDark: "#C2185B", - primaryLight: "rgba(217, 27, 92, 0.1)", - }, - // 中性色 - gray: { - 50: "#F9FAFB", - 100: "#F3F4F6", - 200: "#E5E7EB", - 300: "#D1D5DB", - 400: "#9CA3AF", - 500: "#6B7280", - 600: "#4B5563", - 700: "#374151", - 800: "#1F2937", - 900: "#111827", - }, - // 语义色 - status: { - success: "#10B981", - warning: "#F59E0B", - error: "#EF4444", - info: "#3B82F6", - }, -}; - -export const spacing = { - xs: 4, - sm: 8, - md: 16, - lg: 24, - xl: 32, - "2xl": 40, -}; - -export const radius = { - xs: 4, - sm: 6, - md: 8, - lg: 12, - xl: 16, - full: 9999, -}; - -export const shadows = { - xs: "0 1px 2px rgba(0, 0, 0, 0.05)", - sm: "0 1px 3px rgba(0, 0, 0, 0.1)", - md: "0 4px 6px rgba(0, 0, 0, 0.1)", - lg: "0 10px 15px rgba(0, 0, 0, 0.15)", - xl: "0 10px 40px rgba(0, 0, 0, 0.2)", -}; -``` - -### 第二步:创建全局样式 - -**文件**:`src/assets/styles/globals.css` - -```css -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&display=swap'); - -:root { - --color-brand-primary: #D91B5C; - --color-brand-primary-dark: #C2185B; - --color-brand-primary-light: rgba(217, 27, 92, 0.1); - - --color-gray-50: #F9FAFB; - --color-gray-100: #F3F4F6; - --color-gray-200: #E5E7EB; - --color-gray-300: #D1D5DB; - --color-gray-400: #9CA3AF; - --color-gray-500: #6B7280; - --color-gray-600: #4B5563; - --color-gray-700: #374151; - --color-gray-800: #1F2937; - - --color-success: #10B981; - --color-warning: #F59E0B; - --color-error: #EF4444; - --color-info: #3B82F6; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Noto Sans SC', system-ui, -apple-system, sans-serif; - background-color: #F5F7FA; - color: #1F2937; - line-height: 1.5; -} - -button { - cursor: pointer; - border: none; - font-family: inherit; -} - -input, textarea { - font-family: inherit; -} - -/* 自定义滚动条 */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: #D1D5DB; - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: #9CA3AF; -} -``` - -### 第三步:创建 Framer Motion 动画预设 - -**文件**:`src/utils/animations.ts` - -```typescript -import { Variants } from 'framer-motion'; - -export const fadeInOut: Variants = { - initial: { opacity: 0 }, - animate: { opacity: 1 }, - exit: { opacity: 0 }, - transition: { duration: 0.2 }, -}; - -export const slideUp: Variants = { - initial: { y: 20, opacity: 0 }, - animate: { y: 0, opacity: 1 }, - exit: { y: 20, opacity: 0 }, - transition: { duration: 0.3, ease: 'easeOut' }, -}; - -export const slideInRight: Variants = { - initial: { x: 300, opacity: 0 }, - animate: { x: 0, opacity: 1 }, - exit: { x: 300, opacity: 0 }, - transition: { duration: 0.3, ease: 'easeOut' }, -}; - -export const scaleIn: Variants = { - initial: { scale: 0.9, opacity: 0 }, - animate: { scale: 1, opacity: 1 }, - exit: { scale: 0.9, opacity: 0 }, - transition: { duration: 0.2, ease: 'easeOut' }, -}; - -export const container = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { - staggerChildren: 0.05, - }, - }, -}; - -export const item = { - hidden: { opacity: 0, y: 20 }, - show: { - opacity: 1, - y: 0, - transition: { duration: 0.3 }, - }, -}; -``` - -## 2.2 创建基础组件库 - -### 任务列表 -- [ ] 创建 Button 组件(`src/components/base/Button.tsx`) -- [ ] 创建 Input 组件(`src/components/base/Input.tsx`) -- [ ] 创建 Card 组件(`src/components/base/Card.tsx`) -- [ ] 创建 Badge 组件(`src/components/base/Badge.tsx`) -- [ ] 创建 Modal 组件(`src/components/base/Modal.tsx`) -- [ ] 创建 Spinner 组件(`src/components/base/Spinner.tsx`) - -**参考文档**:`spec/ui-design-components.md` 第 2 节 - -**示例代码**:`src/components/base/Button.tsx` - -```typescript -import React from 'react'; -import clsx from 'clsx'; - -interface ButtonProps { - variant?: 'primary' | 'secondary' | 'outline' | 'ghost'; - size?: 'xs' | 'sm' | 'md' | 'lg'; - disabled?: boolean; - loading?: boolean; - fullWidth?: boolean; - children: React.ReactNode; - onClick?: () => void; - className?: string; - type?: 'button' | 'submit' | 'reset'; -} - -export const Button: React.FC = ({ - variant = 'primary', - size = 'md', - disabled = false, - loading = false, - fullWidth = false, - children, - onClick, - className, - type = 'button', -}) => { - const variantClasses = { - primary: 'bg-brand-primary text-white hover:bg-brand-dark disabled:opacity-50', - secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200 border border-gray-200', - outline: 'bg-transparent text-gray-800 border border-gray-300 hover:bg-gray-50', - ghost: 'bg-transparent text-gray-800 hover:bg-gray-100', - }; - - const sizeClasses = { - xs: 'h-7 px-3 text-xs', - sm: 'h-8 px-4 text-sm', - md: 'h-10 px-5 text-base', - lg: 'h-12 px-6 text-lg', - }; - - return ( - - ); -}; -``` - -## 2.3 创建容器与布局组件 - -### 任务列表 -- [ ] 创建 Layout 组件(`src/components/containers/Layout.tsx`) -- [ ] 创建 Sidebar 组件(`src/components/containers/Sidebar.tsx`) -- [ ] 创建 Header 组件(`src/components/containers/Header.tsx`) - -**参考文档**:`spec/ui-design-components.md` 第 3 节 - -## 2.4 创建业务组件 - -### 任务列表 -- [ ] 创建 MessageBubble 组件(`src/components/features/MessageBubble.tsx`) -- [ ] 创建 SuggestionCard 组件(`src/components/features/SuggestionCard.tsx`) -- [ ] 创建 StatCard 组件(`src/components/features/StatCard.tsx`) -- [ ] 创建 ConversationCard 组件(`src/components/features/ConversationCard.tsx`) - -**参考文档**:`spec/ui-design-components.md` 第 4 节 - -## 2.5 创建状态管理 - -### 文件:`src/store/ui.ts`(Zustand store) - -```typescript -import create from 'zustand'; - -interface UIState { - sidebarOpen: boolean; - toggleSidebar: () => void; - - selectedConversation: string | null; - setSelectedConversation: (id: string | null) => void; - - theme: 'light' | 'dark'; - setTheme: (theme: 'light' | 'dark') => void; -} - -export const useUIStore = create((set) => ({ - sidebarOpen: true, - toggleSidebar: () => set(state => ({ sidebarOpen: !state.sidebarOpen })), - - selectedConversation: null, - setSelectedConversation: (id) => set({ selectedConversation: id }), - - theme: 'dark', - setTheme: (theme) => set({ theme }), -})); -``` - -## 2.6 参考文档 - -| 文档 | 用途 | -|------|------| -| **spec/ui-design-components.md** | 所有组件详细规范 | -| **spec/UI_DESIGN_INDEX.md** | 快速导航组件库 | - ---- - -# 第三阶段:核心功能开发(第 2-3 周) - -## 3.1 音频采集与转录 - -### 第一步:安装音频库 - -```bash -npm install \ - node-record-lpcm16 \ - web-audio-api \ - @types/node -``` - -### 第二步:创建音频服务 - -**文件**:`src/main/services/AudioService.ts` - -参考文档:`spec/audio-capture-tech-note.md` - -```typescript -// 伪代码框架 -class AudioService { - private micStream: any; - private systemAudioStream: any; - - async startCapture() { - // 1. 请求麦克风权限 - // 2. 启动麦克风采集 - // 3. 启动系统音频捕获 - } - - async stopCapture() { - // 停止所有采集 - } - - async getMicAudio(): Promise { - // 获取麦克风音频缓冲区 - } - - async getSystemAudio(): Promise { - // 获取系统音频缓冲区 - } - - async mergeAudio(mic: Buffer, system: Buffer): Promise { - // 混合两路音频 - } -} - -export const audioService = new AudioService(); -``` - -### 第三步:IPC 通信桥接 - -**文件**:`src/main/ipc/audio.ts` - -```typescript -import { ipcMain } from 'electron'; -import { audioService } from '../services/AudioService'; - -export function setupAudioIPC() { - ipcMain.handle('audio:start-capture', async () => { - await audioService.startCapture(); - }); - - ipcMain.handle('audio:stop-capture', async () => { - await audioService.stopCapture(); - }); - - ipcMain.handle('audio:get-mic', async () => { - return audioService.getMicAudio(); - }); -} -``` - -## 3.2 LLM 集成与 API 调用 - -### 第一步:创建 LLM 配置类型 - -**文件**:`src/types/llm.ts` - -```typescript -export interface LLMProvider { - id: string; - name: string; - modelId: string; - apiKey: string; - apiUrl?: string; - isActive: boolean; -} - -export interface LLMResponse { - content: string; - tokens: number; - model: string; -} - -export interface SuggestionRequest { - context: string; - conversationHistory: Message[]; - userMessage: string; -} - -export interface Suggestion { - text: string; - tags: string[]; - expectedImpact: number; -} -``` - -### 第二步:创建 LLM 服务 - -**文件**:`src/main/services/LLMService.ts` - -参考文档:`spec/llm-integration.md` - -```typescript -class LLMService { - private provider: LLMProvider; - - setProvider(provider: LLMProvider) { - this.provider = provider; - } - - async testConnection(): Promise { - // 发送测试请求到 LLM API - // 返回连接成功/失败 - } - - async generateSuggestions(request: SuggestionRequest): Promise { - // 1. 构建提示词(Prompt Engineering) - // 2. 调用 LLM API - // 3. 解析响应,提取建议 - // 4. 返回建议列表 - } - - async analyzeConversation(messages: Message[]): Promise { - // 1. 构建分析提示词 - // 2. 调用 LLM API - // 3. 返回分析结果 - } -} - -export const llmService = new LLMService(); -``` - -## 3.3 数据模型与存储 - -### 第一步:安装 SQLite 库 - -```bash -npm install better-sqlite3 @types/better-sqlite3 -``` - -### 第二步:创建数据库初始化脚本 - -**文件**:`src/main/db/init.ts` - -参考文档:`spec/data-model.md` - -```typescript -import Database from 'better-sqlite3'; -import path from 'path'; - -const dbPath = path.join(process.env.APPDATA || process.env.HOME, 'LiveGalGame', 'app.db'); - -export const db = new Database(dbPath); - -export function initializeDatabase() { - // 创建表 - db.exec(` - CREATE TABLE IF NOT EXISTS person ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - nickname TEXT, - personality_desc TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - - CREATE TABLE IF NOT EXISTS conversation ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, - person_id TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (person_id) REFERENCES person(id) - ); - - CREATE TABLE IF NOT EXISTS turn ( - id TEXT PRIMARY KEY, - conversation_id TEXT NOT NULL, - role TEXT NOT NULL, - content TEXT NOT NULL, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - is_key_point BOOLEAN DEFAULT FALSE, - FOREIGN KEY (conversation_id) REFERENCES conversation(id) - ); - - CREATE TABLE IF NOT EXISTS score ( - id TEXT PRIMARY KEY, - conversation_id TEXT NOT NULL, - previous_score REAL, - current_score REAL, - delta REAL, - turn_id TEXT, - FOREIGN KEY (conversation_id) REFERENCES conversation(id), - FOREIGN KEY (turn_id) REFERENCES turn(id) - ); - `); -} -``` - -## 3.4 参考文档 - -| 文档 | 用途 | -|------|------| -| **spec/audio-capture-tech-note.md** | 音频采集实现细节 | -| **spec/llm-integration.md** | LLM 集成与提示工程 | -| **spec/data-model.md** | SQLite 表设计 | - ---- - -# 第四阶段:页面与业务逻辑集成(第 3-4 周) - -## 4.1 Electron 主窗口与 HUD 浮窗 - -### 第一步:创建主进程入口 - -**文件**:`src/main/index.ts` - -```typescript -import { app, BrowserWindow, ipcMain } from 'electron'; -import path from 'path'; -import { isDev } from './utils'; -import { setupAudioIPC } from './ipc/audio'; -import { setupLLMIPC } from './ipc/llm'; -import { initializeDatabase } from './db/init'; - -let mainWindow: BrowserWindow; -let chatWindow: BrowserWindow; - -async function createMainWindow() { - mainWindow = new BrowserWindow({ - width: 1400, - height: 900, - minWidth: 1200, - minHeight: 800, - webPreferences: { - preload: path.join(__dirname, 'preload.ts'), - contextIsolation: true, - nodeIntegration: false, - }, - }); - - const startUrl = isDev - ? 'http://localhost:8080' - : `file://${path.join(__dirname, '../../dist/index.html')}`; - - mainWindow.loadURL(startUrl); - - if (isDev) { - mainWindow.webContents.openDevTools(); - } -} - -async function createChatWindow() { - chatWindow = new BrowserWindow({ - width: 440, - height: 700, - minWidth: 360, - minHeight: 480, - alwaysOnTop: true, - transparent: true, - frame: false, - webPreferences: { - preload: path.join(__dirname, 'preload.ts'), - contextIsolation: true, - }, - }); - - const startUrl = isDev - ? 'http://localhost:8080/chat' - : `file://${path.join(__dirname, '../../dist/index.html')}`; - - chatWindow.loadURL(startUrl); -} - -app.on('ready', async () => { - initializeDatabase(); - setupAudioIPC(); - setupLLMIPC(); - - await createMainWindow(); - await createChatWindow(); -}); - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit(); - } -}); -``` - -### 第二步:创建 React 路由 - -**文件**:`src/renderer/App.tsx` - -```typescript -import React from 'react'; -import { BrowserRouter, Routes, Route } from 'react-router-dom'; -import { Layout } from '@components/containers/Layout'; -import Dashboard from '@pages/Dashboard'; -import LLMConfig from '@pages/LLMConfig'; -import ConversationDetail from '@pages/ConversationDetail'; -import ChatWindow from '@pages/ChatWindow'; - -export function App() { - return ( - - - {/* 主窗口路由 */} - } /> - } /> - } /> - - {/* HUD 浮窗路由 */} - } /> - - - ); -} -``` - -## 4.2 页面开发顺序 - -### 优先级 P0(第一周完成) - -1. **Dashboard(主页)** - `src/pages/Dashboard.tsx` - - 参考:`spec/ui-design-03-dashboard.md` - - 包含:欢迎区、统计卡片、对话列表、新建对话 - - 预计时间:2-3 天 - - 依赖组件:StatCard, ConversationCard, Header - -2. **LLMConfig(配置页)** - `src/pages/LLMConfig.tsx` - - 参考:`spec/ui-design-02-llm-config.md` - - 包含:模型卡片、添加模型、连接测试 - - 预计时间:2-3 天 - - 依赖组件:Card, Button, Input, Modal - -3. **ChatWindow(HUD 浮窗)** - `src/pages/ChatWindow.tsx` - - 参考:`spec/ui-design-01-chat-window.md` - - 包含:对话气泡、AI 建议、状态指示、操作栏 - - 预计时间:3-4 天 - - 依赖组件:MessageBubble, SuggestionCard, Button, Spinner - -### 优先级 P1(第二周完成) - -4. **ConversationDetail(对话详情)** - `src/pages/ConversationDetail.tsx` - - 参考:`spec/ui-design-04-conversation-detail.md` - - 包含:对话内容、AI 分析、好感度曲线、消息编辑 - - 预计时间:4-5 天 - - 依赖组件:MessageBubble, Card, Button, Modal, Chart - -## 4.3 业务逻辑集成 - -### 创建自定义 Hooks - -**文件**:`src/hooks/useConversation.ts` - -```typescript -import { useQuery, useMutation } from '@react-query/core'; -import { conversationService } from '@utils/api'; - -export function useConversation(id: string) { - return useQuery(['conversation', id], () => - conversationService.getConversation(id) - ); -} - -export function useCreateConversation() { - return useMutation((data) => - conversationService.createConversation(data) - ); -} - -export function useSaveMessage() { - return useMutation(({ conversationId, message }) => - conversationService.addMessage(conversationId, message) - ); -} -``` - -## 4.4 参考文档 - -| 文档 | 用途 | 优先级 | -|------|------|-------| -| **spec/ui-design-03-dashboard.md** | Dashboard 页面规范 | P0 | -| **spec/ui-design-02-llm-config.md** | LLM 配置页规范 | P0 | -| **spec/ui-design-01-chat-window.md** | Chat HUD 规范 | P0 | -| **spec/ui-design-04-conversation-detail.md** | 对话详情页规范 | P1 | -| **spec/hud-ux.md** | HUD 交互细节 | P0 | - ---- - -# 第五阶段:打包、测试与优化(第 4-5 周) - -## 5.1 单元测试与集成测试 - -### 第一步:编写测试用例 - -**文件**:`tests/unit/Button.test.tsx` - -```typescript -import { render, screen, fireEvent } from '@testing-library/react'; -import { Button } from '@components/base/Button'; - -describe('Button Component', () => { - it('renders button with correct text', () => { - render(); - expect(screen.getByText('Click me')).toBeInTheDocument(); - }); - - it('calls onClick handler when clicked', () => { - const handleClick = jest.fn(); - render(); - - fireEvent.click(screen.getByText('Click')); - expect(handleClick).toHaveBeenCalled(); - }); - - it('applies primary variant styles', () => { - render(); - const button = screen.getByText('Submit'); - expect(button).toHaveClass('bg-brand-primary'); - }); -}); -``` - -### 第二步:运行测试 - -```bash -npm test # 运行一次 -npm run test:watch # 监听模式 -``` - -## 5.2 性能优化 - -### 参考清单 - -- [ ] 组件使用 React.memo 避免不必要重渲染 -- [ ] 使用 useCallback 缓存函数 -- [ ] 列表使用虚拟化(react-window)处理大数据量 -- [ ] 图片优化(使用 WebP 格式) -- [ ] 代码分割(code splitting) - -参考文档:`spec/ui-design-components.md` 第 10 节 - -## 5.3 构建和打包 - -### 第一步:编译应用 - -```bash -npm run build -``` - -### 第二步:生成应用程序包 - -```bash -# Windows -npm run package:win - -# macOS -npm run package:mac - -# 两个平台 -npm run package -``` - -参考文档:`spec/build-and-release.md` - -## 5.4 参考文档 - -| 文档 | 用途 | -|------|------| -| **spec/test-plan.md** | 完整测试计划 | -| **spec/build-and-release.md** | 打包与发版流程 | - ---- - -# 第六阶段:发版与迭代(第 5-6 周) - -## 6.1 版本发布 - -### 第一步:更新版本号 - -```bash -# package.json -{ - "version": "1.0.0" -} -``` - -### 第二步:生成 CHANGELOG - -``` -## v1.0.0 (2025-12-XX) - -### 新增 -- ✅ 对话 HUD 浮窗实现 -- ✅ AI 建议生成 -- ✅ LLM 模型配置 -- ✅ 对话数据存储与分析 - -### 修复 -- 修复音频采集延迟问题 -- 修复 macOS 透明窗口适配 - -### 性能 -- 优化消息列表虚拟化 -- 减少内存占用 30% -``` - -### 第三步:发布应用 - -```bash -npm run release -``` - -参考文档:`spec/build-and-release.md` - -## 6.2 用户反馈与迭代 - -- 收集用户反馈(GitHub Issues、用户调查等) -- 优先级排序(Critical > High > Medium > Low) -- 规划下一版本(v1.1) - -## 6.3 持续集成/部署 - -参考文档:`.github/workflows/release.yml` - ---- - -# 开发检查清单 - -## ✅ 第一阶段完成标志 - -- [ ] 所有依赖安装完毕 -- [ ] 目录结构创建完整 -- [ ] TypeScript 配置正确 -- [ ] `npm run build` 成功编译 -- [ ] Webpack dev server 正常启动 - -## ✅ 第二阶段完成标志 - -- [ ] 所有基础组件完成(6 个) -- [ ] 容器组件完成(3 个) -- [ ] 业务组件完成(4 个) -- [ ] 设计系统配置(令牌、样式、动画) -- [ ] Storybook 可视化展示所有组件 -- [ ] 组件单元测试覆盖率 > 80% - -## ✅ 第三阶段完成标志 - -- [ ] 音频采集功能正常工作 -- [ ] LLM API 连接测试成功 -- [ ] 数据库初始化完成 -- [ ] IPC 通信桥接建立 -- [ ] 本地存储正常读写 - -## ✅ 第四阶段完成标志 - -- [ ] Dashboard 页面完整实现 -- [ ] LLM Config 页面完整实现 -- [ ] Chat HUD 浮窗完整实现 -- [ ] Conversation Detail 页面完整实现 -- [ ] 页面间导航正常工作 -- [ ] 功能测试通过(对照 spec/test-plan.md) - -## ✅ 第五阶段完成标志 - -- [ ] 单元测试覆盖率 > 80% -- [ ] 集成测试通过 -- [ ] 性能指标达标(首屏 < 2s,帧率 60fps) -- [ ] Windows 和 macOS 打包成功 -- [ ] 应用程序能正常安装和运行 - -## ✅ 第六阶段完成标志 - -- [ ] v1.0.0 发版成功 -- [ ] 用户反馈收集与整理完成 -- [ ] v1.1 迭代计划制定完成 - ---- - -# 快速命令参考 - -```bash -# 开发 -npm run dev # 启动开发服务器 + Electron -npm run webpack:dev # 仅启动 Webpack dev server -npm start # 仅启动 Electron - -# 构建 -npm run build # 编译 React + TypeScript -npm run package # 打包应用程序(Win + Mac) -npm run package:win # 仅打包 Windows -npm run package:mac # 仅打包 macOS - -# 测试 -npm test # 运行测试 -npm run test:watch # 测试监听模式 - -# 文档 -npm run storybook # 启动 Storybook(http://localhost:6006) - -# 代码质量 -npm run lint # 检查代码风格 -``` - ---- - -# 常见问题(FAQ) - -## Q1:如何在 macOS 上处理代码签名? - -A:参考 `spec/build-and-release.md` 第 3.2 节(macOS 硬化与公证) - -## Q2:如何添加代理以加速下载? - -A:根据用户规则,使用 `dl1` 命令启动代理,并配置 npm: -```bash -dl1 # 启动代理 -npm config set registry http://registry.proxy.local -``` - -## Q3:音频采集在 Linux 上支持吗? - -A:当前设计仅支持 Windows 和 macOS,Linux 支持需要后续扩展 - -## Q4:如何本地测试 LLM 集成? - -A:在 `spec/llm-integration.md` 中找到本地测试脚本 - -## Q5:如何生成热更新? - -A:参考 `spec/build-and-release.md` 第 3.4 节(自动更新配置) - ---- - -# 进阶主题 - -## 性能监控 - -参考 `spec/ui-design-components.md` 第 10 节性能优化部分 - -## 无障碍支持 - -参考 `spec/ui-design-components.md` 第 11 节无障碍指南 - -## 国际化(i18n) - -当前版本仅支持中文,国际化支持需要后续规划 - ---- - -# 支持与联系 - -- **技术支持**:team@livegalgame.local -- **Bug 报告**:GitHub Issues -- **功能建议**:Discussions -- **设计反馈**:Figma 评论 - ---- - -**文档版本**:v1.0 -**最后更新**:2025-11-12 -**预计完成时间**:4-6 周(取决于团队规模) - diff --git a/desktop/DEVELOPMENT_PLAN.md b/desktop/DEVELOPMENT_PLAN.md new file mode 100644 index 0000000..ce14369 --- /dev/null +++ b/desktop/DEVELOPMENT_PLAN.md @@ -0,0 +1,441 @@ +# LiveGalGame Desktop 开发计划 + +## 开发原则 +- **MVP优先**:从最简可行产品开始,逐步迭代 +- **渐进式开发**:每一步完成后立即测试运行,确保无报错 +- **Mock先行**:LLM/语音识别等接口先使用Mock数据 +- **参考官方文档**:遇到不确定的用法时,查阅 [Electron官方文档](https://www.electronjs.org/zh/docs/latest/) + +## 技术栈 +- **框架**:Electron 33+ (最新稳定版) +- **包管理器**:pnpm 8+ +- **UI框架**:原生HTML + Tailwind CSS +- **前端**:Vanilla JavaScript (无框架,降低复杂度) +- **构建工具**:electron-builder + +## 核心模块 +1. **主进程** (main.js) - Electron应用入口 +2. **主窗口** - 对话管理和配置界面 +3. **HUD浮窗** - 实时辅助界面 +4. **音频采集** - 麦克风/系统音频 +5. **Mock LLM服务** - 模拟AI建议 +6. **数据存储** - 本地JSON存储 + +--- + +## MVP 分阶段开发计划 + +### 阶段 0: 基础项目搭建 +**目标**:创建可运行的Electron项目骨架 + +**步骤 0.1**: 初始化项目 +- 创建 `package.json` +- 配置 pnpm + Electron 33 +- 添加基础脚本: `dev`, `build` +- 验证: `pnpm dev` 能启动空窗口 + +**【确认点】** 请验证: +1. 项目初始化成功,无安装错误 +2. `pnpm dev` 能启动Electron窗口 +3. 确认后我将进入下一步 + +**步骤 0.2**: 基础文件结构 +- `main.js` - 主进程入口 +- `preload.js` - 预加载脚本(安全上下文) +- `renderer/` - 渲染进程代码 +- 验证: 主窗口正常显示 + +**【确认点】** 请验证: +1. 文件结构创建完成 +2. `pnpm dev` 能正常显示主窗口 +3. 确认后我将进入下一步 + +**步骤 0.3**: 开发环境配置 +- 热重载配置 +- 开发者工具集成 +- 日志系统 +- 验证: 修改代码自动重载 + +**【确认点】** 请验证: +1. 热重载工作正常 +2. 开发者工具可用 +3. 确认后阶段0完成,进入下一阶段 + +--- + +### 阶段 1: 主窗口UI框架 (Day 1) +**目标**:实现主窗口的基础UI,参考原型图 + +**步骤 1.1**: 窗口配置 +- 设置窗口尺寸 (1200x800) +- 配置标题栏和图标 +- 添加快捷键 (Ctrl+R 刷新, Ctrl+Shift+I 开发者工具) +- 验证: 窗口正常显示,快捷键有效 + +**【确认点】** 请验证: +1. 窗口尺寸设置正确 +2. 快捷键功能正常 +3. 确认后我将进入下一步 + +**步骤 1.2**: 基础布局 +- 左侧导航栏(对话列表) +- 中间主内容区(对话详情) +- 右侧AI分析面板 +- 使用Tailwind CSS实现样式 +- 验证: 三栏布局正常显示 + +**【确认点】** 请验证: +1. 三栏布局正确显示 +2. Tailwind CSS样式生效 +3. 确认后我将进入下一步 + +**步骤 1.3**: 对话列表组件 +- 硬编码几个示例对话 +- 头像 + 名称 + 最后消息 +- 选中状态高亮 +- 验证: 能切换不同对话 + +**步骤 1.4**: 对话详情组件 +- 消息气泡(对方/自己) +- 时间戳 +- AI高亮显示 +- 验证: 消息正确显示 + +**步骤 1.5**: AI分析面板 +- AI洞察区域 +- 对话分类标签 +- 情感分析 +- 验证: 静态数据正确显示 + +--- + +### 阶段 2: HUD浮窗基础 (Day 2) +**目标**:创建可浮动的HUD窗口 + +**步骤 2.1**: HUD窗口创建 +- 创建无边框、透明窗口 +- 设置初始位置 (右下角) +- 配置置顶/穿透模式 +- 验证: HUD窗口能显示 + +**步骤 2.2**: HUD UI基础 +- 双通道转录区域(对方/自己) +- 最小化/收起按钮 +- 简单样式(半透明背景) +- 验证: UI元素正常显示 + +**步骤 2.3**: 主窗口与HUD通信 +- IPC通信机制 +- 主窗口按钮"开始对话" → 显示HUD +- HUD关闭 → 返回主窗口 +- 验证: 双向通信正常 + +**步骤 2.4**: HUD交互 +- 拖拽移动 +- 鼠标穿透切换 +- 自动收起/展开 +- 验证: 交互功能正常 + +--- + +### 阶段 3: 音频采集与权限 (Day 3) +**目标**:实现麦克风权限请求和基础音频流 + +**步骤 3.1**: 权限请求系统 +- 首次启动权限检测 +- 麦克风权限请求UI +- 权限状态存储 +- 验证: 能正确请求权限 + +**步骤 3.2**: 音频测试组件 +- 连接测试按钮 +- 音频波形可视化 +- 测试录音/播放 +- 验证: 能看到音频波形 + +**步骤 3.3**: 用户麦克风采集 +- 使用 Web Audio API +- 获取麦克风音频流 +- 音频数据处理 +- 验证: 能采集麦克风音频 + +**步骤 3.4**: 音频状态管理 +- 录音状态(开始/停止) +- 音量检测 +- 错误处理 +- 验证: 状态切换正常 + +--- + +### 阶段 4: Mock语音识别和LLM集成 (Day 4-5) +**目标**:实现Mock的AI建议系统 + +**步骤 4.1**: Mock语音识别服务 +- 创建 `mock-asr.js` +- 模拟语音转文字(随机延迟2-3秒) +- 模拟对方音频转文字 +- 验证: 能返回Mock文本 + +**步骤 4.2**: Mock LLM服务 +- 创建 `mock-llm.js` +- 模拟建议卡生成(基于关键词) +- 模拟情感分析 +- 验证: 能返回Mock建议 + +**步骤 4.3**: 建议卡UI +- 建议卡片组件 +- 标题 + 一句话 + 标签 +- 好感度变化预测 +- 验证: 建议卡正常显示 + +**步骤 4.4**: HUD集成AI建议 +- 监听对话内容 +- 触发建议生成 +- 在HUD显示建议卡 +- 验证: 建议能实时显示 + +**步骤 4.5**: 建议交互 +- 点击选择建议 +- 编辑框弹出 +- 一键复制 +- 验证: 交互功能正常 + +--- + +### 阶段 5: 即时反馈系统 (Day 6) +**目标**:实现好感度动画和反馈 + +**步骤 5.1**: 好感度状态 +- 创建好感度数据模型 +- 初始值50 +- 存储和更新逻辑 +- 验证: 状态管理正常 + +**步骤 5.2**: 好感度动画 +- 数字变化动画(50→70) +- "+20"上浮效果 +- 颜色变化(红色/绿色) +- 验证: 动画流畅 + +**步骤 5.3**: 反馈解释 +- 轻量解释文本 +- 显示3秒后自动消失 +- 位置在HUD底部 +- 验证: 显示和消失正常 + +**步骤 5.4**: 触发机制 +- 监听用户发送动作 +- 计算好感度变化 +- 触发动画和解释 +- 验证: 完整流程正常 + +--- + +### 阶段 6: 数据模型和存储 (Day 7) +**目标**:实现本地数据存储 + +**步骤 6.1**: 对话数据模型 +- 人物档案(昵称、关系标签、备注) +- 对话记录(时间、内容、发送者) +- AI分析结果 +- 验证: 数据结构合理 + +**步骤 6.2**: 本地存储实现 +- 使用 `electron-store` +- 对话列表持久化 +- 配置持久化 +- 验证: 重启后数据保留 + +**步骤 6.3**: CRUD操作 +- 创建新对话 +- 编辑对话信息 +- 删除对话 +- 保存对话记录 +- 验证: 所有操作正常 + +**步骤 6.4**: 数据导入/导出 +- 导出对话为JSON +- 从文件导入 +- 验证: 导入导出正常 + +--- + +### 阶段 7: 复盘分析功能 (Day 8) +**目标**:实现对话复盘和报告 + +**步骤 7.1**: 复盘页面 +- 创建复盘路由/页面 +- 人物详情展示 +- 对话统计(次数、时间、好感度) +- 验证: 页面正常显示 + +**步骤 7.2**: 可视化组件 +- 好感度变化曲线图 +- 关键事件时间轴 +- 使用Chart.js +- 验证: 图表正常显示 + +**步骤 7.3**: AI复盘报告 +- Mock复盘分析文本 +- "做得好"区域 +- "可改进"区域 +- "下次建议"区域 +- 验证: 报告内容显示 + +**步骤 7.4**: 报告导出 +- 导出为Markdown +- 复制到剪贴板 +- 验证: 导出功能正常 + +--- + +### 阶段 8: 设置和配置 (Day 9) +**目标**:实现设置页面 + +**步骤 8.1**: 设置页面UI +- 模型选择(结果导向文案) +- API Key输入 +- Endpoint配置 +- 验证: UI正常显示 + +**步骤 8.2**: 连接测试 +- 测试连接按钮 +- 延迟/速率显示 +- 成功/失败反馈 +- 验证: 测试功能正常 + +**步骤 8.3**: 多配置管理 +- 添加多个配置 +- 设置默认配置 +- 星标标记 +- 验证: 配置切换正常 + +**步骤 8.4**: 代理配置 +- 代理设置UI +- `dl1`命令集成(可选) +- 验证: 配置保存正常 + +--- + +### 阶段 9: 高级音频功能 (Day 10) +**目标**:系统音频采集(平台相关) + +**步骤 9.1**: Windows系统音频 +- 使用 `desktopCapturer` + WASAPI +- 获取系统音频流 +- 测试和验证 +- 验证: Windows上能捕获系统音频 + +**步骤 9.2**: macOS系统音频 +- 屏幕录制权限请求 +- 音频采集实现 +- 虚拟声卡方案说明 +- 验证: macOS上能捕获系统音频 + +**步骤 9.3**: 音频混合 +- 用户音频 + 系统音频 +- 分别处理和识别 +- 验证: 双通道音频正常 + +--- + +### 阶段 10: 完善和测试 (Day 11-12) +**目标**:完善细节,全面测试 + +**步骤 10.1**: 权限和安全 +- 完整的权限检查流程 +- 隐私说明文案 +- 安全最佳实践 +- 验证: 权限流程完善 + +**步骤 10.2**: 错误处理 +- 网络错误处理 +- API调用失败降级 +- 用户友好错误提示 +- 验证: 错误场景覆盖 + +**步骤 10.3**: 性能优化 +- 内存泄漏检查 +- 启动速度优化 +- 响应速度优化 +- 验证: 性能良好 + +**步骤 10.4**: 打包和分发 +- electron-builder配置 +- Windows打包 (build:win) +- macOS打包 (build:mac) +- 验证: 能生成安装包 + +**步骤 10.5**: 完整流程测试 +- Onboarding流程 +- 实时对话流程 +- 复盘分析流程 +- 验证: 所有流程正常 + +--- + +## 开发流程规范 + +### 关键流程要求 +⚠️ **重要规定**:每个开发步骤完成后,必须立即编译测试,确认无报错且功能正常后,**需要得到用户确认**才能进行下一步。 + +### 测试验证清单 +每个步骤完成后,必须进行以下验证: +- [ ] 代码无语法错误,能正常编译 +- [ ] 应用能正常启动,不闪退 +- [ ] 新增功能按预期工作 +- [ ] 无控制台报错或警告 +- [ ] 原有功能不受影响 +- [ ] **用户确认通过** ✅ + +### 代码规范 +- 使用ES6+语法 +- 语义化变量命名 +- 添加必要注释 +- 遵循Electron安全最佳实践 + +### 提交规范 +``` +feat: 添加新功能 +fix: 修复bug +docs: 文档更新 +style: 代码格式 +refactor: 重构 +perf: 性能优化 +test: 测试相关 +chore: 构建/工具 +``` + +### 确认点标记 +在开发计划中,每个步骤末尾都有明确的确认点,例如: +**【确认点】** 请验证此步骤已完成且测试通过,确认后我将继续下一步。 + +--- + +## 参考资料 +- [Electron官方文档](https://www.electronjs.org/zh/docs/latest/) +- 产品原型图: `/desktop/spec/images/code.html` +- 技术文档: `/desktop/spec/` +- 参考Demo: `/minimal-repro/` + +--- + +## 风险和对策 + +| 风险 | 对策 | +|------|------| +| 音频采集跨平台兼容性 | 分平台实现,详细测试 | +| 权限请求被拒 | 友好提示,提供手动设置指南 | +| LLM响应延迟高 | Mock数据先行,后续优化流式处理 | +| HUD窗口管理复杂 | 逐步添加功能,确保稳定性 | +| 性能问题 | 定期性能测试,及时优化 | + +--- + +## 下一步 +完成本计划后,可以根据用户反馈进行: +1. 接入真实的ASR和LLM服务 +2. 添加更多可视化图表 +3. 优化UI/UX细节 +4. 添加云同步功能 +5. 支持更多平台特性 diff --git a/desktop/PROMPT_SUMMARY.md b/desktop/PROMPT_SUMMARY.md new file mode 100644 index 0000000..d8b1763 --- /dev/null +++ b/desktop/PROMPT_SUMMARY.md @@ -0,0 +1,151 @@ +# LiveGalGame Desktop 开发进度与下一步提示词 + +## 当前完成状态 (2025-11-13) + +### ✅ 阶段 0: 基础项目搭建 (100%) +- [x] Electron 33 + pnpm 初始化 +- [x] 主进程 (main.js) 和预加载脚本 (preload.js) +- [x] 开发环境配置和热重载 +- [x] 基础文件结构创建 + +### ✅ 阶段 1: 主窗口UI框架 (100%) +- [x] 窗口配置 (1200x800, 快捷键 Ctrl+R/Ctrl+Shift+I) +- [x] 三栏布局优化 +- [x] 对话列表组件 (支持切换) +- [x] 对话详情组件 (消息气泡) +- [x] AI分析面板 (动态洞察 + 标签管理) +- [x] **页面跳转系统** (总览 ↔ 对话编辑器) + +### ✅ 阶段 2: HUD浮窗基础 (50%) +- [x] HUD窗口创建 (无边框、透明、右下角) +- [x] HUD UI基础 (双通道转录 + AI建议卡片) +- [x] 主窗口与HUD通信 (IPC机制) +- [ ] **待完成**: 从主窗口触发HUD显示 +- [ ] **待完成**: HUD拖拽移动 +- [ ] **待完成**: 鼠标穿透切换 + +## 核心架构说明 + +### 主进程 (main.js) +- `mainWindow`: 主窗口 (1200x800) +- `hudWindow`: HUD浮窗 (400x300, 右下角) +- IPC通信: `show-hud`, `hide-hud`, `close-hud` + +### 渲染进程 +- **index.html**: 总览页 + - 左侧导航 (粉色渐变) + - 统计卡片 (4个) + - 最近对话列表 +- **conversation-editor.html**: 对话编辑器 + - 三栏布局 (对话列表/详情/AI分析) + - 对话切换功能 +- **hud.html**: HUD浮窗 + - 双通道转录 (对方/我) + - AI建议卡片 + - 最小化/关闭按钮 + +### 预加载脚本 (preload.js) +暴露API: +- `window.electronAPI.showHUD()` +- `window.electronAPI.hideHUD()` +- `window.electronAPI.closeHUD()` + +## 当前问题 + +### 🔧 需要修复 +1. **HUD触发按钮无效** + - 点击"实时助手"按钮,HUD未显示 + - 可能原因: `window.electronAPI.showHUD()` 未正确调用 + - 日志显示: `Show HUD button clicked` 但没有后续 + +2. **需要测试** + - 页面跳转 (总览 ↔ 对话编辑器) + - 对话切换功能 + - HUD窗口样式 + +## 下一步开发计划 + +### 🎯 阶段 2.3: 完成HUD触发 (优先级: 高) +1. 修复 `window.electronAPI.showHUD()` 调用 +2. 添加"开始对话"按钮 +3. 测试HUD显示/隐藏 +4. 验证IPC通信正常 + +### 🎯 阶段 2.4: HUD交互 (优先级: 中) +1. 拖拽移动功能 +2. 鼠标穿透切换 +3. 自动收起/展开 +4. ESC键最小化 + +### 🎯 阶段 3: 音频采集 (优先级: 高) +1. 麦克风权限请求UI +2. 音频测试组件 +3. Web Audio API集成 +4. 音量检测 + +## 关键代码位置 + +``` +src/ +├── main.js # 主进程 (HUD创建 + IPC) +├── preload.js # 预加载 (暴露API) +└── renderer/ + ├── index.html # 总览页 + ├── conversation-editor.html # 对话编辑器 + ├── hud.html # HUD浮窗 + └── js/renderer.js # 渲染进程脚本 +``` + +## 测试命令 + +```bash +cd /Users/cccmmmdd/LiveGalGame/desktop +pnpm dev +``` + +## 功能测试清单 + +### 页面导航 +- [ ] 总览页 → 对话编辑器 (导航菜单) +- [ ] 对话编辑器 → 总览页 (返回按钮) +- [ ] 点击对话卡片跳转 + +### 对话编辑器 +- [ ] 切换对话 (Miyu/Akira/Hana) +- [ ] 查看消息气泡 +- [ ] AI洞察更新 +- [ ] 添加/删除标签 + +### HUD浮窗 +- [ ] 点击"实时助手"打开HUD +- [ ] HUD显示在右下角 +- [ ] 显示转录内容 +- [ ] 显示AI建议 +- [ ] 最小化/关闭按钮 + +## 提示词 (给后续LLM) + +你是LiveGalGame Desktop的开发助手。当前任务是: + +1. **修复HUD触发问题** + - 用户点击"实时助手"按钮后,HUD窗口应该显示 + - 检查 `window.electronAPI.showHUD()` 是否正确调用 + - 确保IPC通信正常 (`ipcMain.on('show-hud')`) + - 验证HUD窗口 (`createHUDWindow()`) 是否正确创建 + +2. **实现阶段 2.4** + - 拖拽移动功能 + - 鼠标穿透切换 + - ESC键最小化 + +3. **准备阶段 3** + - 麦克风权限请求UI + - 音频测试组件 + +**重要**: 每个步骤完成后立即测试,确认无报错后再继续。 + +## 相关文档 + +- 完整开发计划: `/Users/cccmmmdd/LiveGalGame/desktop/DEVELOPMENT_PLAN.md` +- README: `/Users/cccmmmdd/LiveGalGame/desktop/README.md` +- HUD UI: `/Users/cccmmmdd/LiveGalGame/desktop/src/renderer/hud.html` diff --git a/desktop/README.md b/desktop/README.md index 9364fe3..f74af8c 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -21,10 +21,63 @@ LiveGalGame Desktop 是基于 Electron 的跨平台桌面应用(Windows/macOS - spec/test-plan.md 测试计划与验收标准 本地开发(概览) -1) Node.js 20+,pnpm 8+(建议) -2) 克隆仓库并安装依赖:`pnpm install` -3) 开发启动:`pnpm dev`(主进程 + 渲染器热重载) -4) 打包:`pnpm build:win` / `pnpm build:mac`(详见 spec/build-and-release.md) +1) Node.js 20+,pnpm 8+(建议) +2) 克隆仓库并安装依赖:`pnpm install` +3) 开发启动:`pnpm dev`(主进程 + 渲染器热重载) +4) 打包:`pnpm build:win` / `pnpm build:mac`(详见 spec/build-and-release.md) + +## 快速启动命令 + +### 安装依赖 +```bash +cd /Users/cccmmmdd/LiveGalGame/desktop +pnpm install +``` + +### 开发模式启动 +```bash +pnpm dev +``` +启动后,Electron 窗口会自动打开,默认显示主界面。开发模式下会自动打开开发者工具。 + +### 页面导航 +- **总览**: 显示统计数据和最近对话 +- **攻略对象**: 管理所有攻略对象(即将推出) +- **对话编辑器**: 编辑对话内容(即将推出) +- **LLM 配置**: 配置 AI 模型(即将推出) +- **设置**: 应用设置(即将推出) + +### 快捷键 +- **Ctrl+R** (Windows/Linux) 或 **Cmd+R** (macOS): 刷新窗口 +- **Ctrl+Shift+I** (Windows/Linux) 或 **Cmd+Shift+I** (macOS): 打开/关闭开发者工具 +- **ESC**: 预留快捷键(后续用于 HUD 最小化) + +### 功能测试步骤 +1. **页面导航**: 点击左侧导航菜单(总览/攻略对象/对话编辑器等),查看页面切换 + - 点击"对话编辑器"导航菜单,应该跳转到对话编辑器页面 + - 点击"返回总览"按钮,应该回到总览页 +2. **统计数据**: 查看总览页的4个统计卡片(攻略对象/对话/分支/故事标记) +3. **最近对话**: 查看对话卡片列表,悬停显示编辑按钮 +4. **创建对话**: 点击"新对话"按钮或"创建新对话"卡片,应该跳转到对话编辑器 +5. **实时助手**: 点击"实时助手"按钮(后续将打开HUD浮窗) + +页面跳转已实现: +- 总览页 → 对话编辑器:左侧导航菜单或"创建新对话"卡片 +- 对话编辑器 → 总览页:左侧"返回总览"按钮 + +### 生产环境构建 +```bash +# Windows 安装包 +pnpm build:win + +# macOS 安装包 +pnpm build:mac + +# 或通用构建 +pnpm build +``` + +构建输出在 `dist/` 目录下。 网络与下载加速(可选) - 若需要下载外部依赖(如语音/ASR 模型或静态资源),可先执行本地代理命令 `dl1` 来启用代理以加速;大文件下载建议采用多进程/分片并发方式(实现细节在后续实现阶段落地)。 @@ -33,7 +86,31 @@ LiveGalGame Desktop 是基于 Electron 的跨平台桌面应用(Windows/macOS - Windows 10 19045+(x64/arm64 可选)、macOS 12+(Intel/Apple Silicon) - 麦克风可用;若需捕获系统音频:Windows 无需额外驱动,macOS 需屏幕录制权限或使用虚拟声卡 -版权与许可 -根据企业内部策略补充。默认保留所有权利。 +## 开发进度 + +### 已完成阶段 +- **阶段 0**: 基础项目搭建 ✅ + - Electron 项目初始化 + - 基础文件结构创建 + - 开发环境配置 + +- **阶段 1**: 主窗口UI框架 ✅ + - 窗口配置和快捷键 + - 三栏布局(对话列表/对话详情/AI分析) + - 对话列表组件(支持切换) + - 对话详情组件(消息气泡) + - AI分析面板(动态洞察、标签管理) + +### 下一步计划 +- **阶段 1.6**: ✅ 已完成 - 将对话编辑器独立为单独页面 `conversation-editor.html` +- **阶段 2**: HUD浮窗基础(进行中) +- **阶段 3**: 音频采集与权限 +- **阶段 4**: Mock语音识别和LLM集成 +- **阶段 5**: 即时反馈系统(好感度动画) + +完整开发计划详见:`/Users/cccmmmdd/LiveGalGame/desktop/DEVELOPMENT_PLAN.md` + +## 版权与许可 +根据企业内部策略补充。默认保留所有权利。 diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 0000000..90beebb --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,45 @@ +{ + "name": "livegalgame-desktop", + "version": "0.1.0", + "description": "LiveGalGame Desktop - 实时对话辅助与学习工具", + "main": "src/main.js", + "scripts": { + "dev": "electron . --enable-logging", + "build": "electron-builder", + "build:win": "electron-builder --win", + "build:mac": "electron-builder --mac" + }, + "keywords": [ + "electron", + "conversation", + "ai-assistant", + "desktop" + ], + "author": "LiveGalGame Team", + "license": "MIT", + "devDependencies": { + "electron": "^33.2.0", + "electron-builder": "^25.1.8" + }, + "dependencies": { + "electron-store": "^8.2.0" + }, + "build": { + "appId": "com.livegalgame.desktop", + "productName": "LiveGalGame Desktop", + "directories": { + "output": "dist" + }, + "files": [ + "src/**/*", + "assets/**/*", + "node_modules/**/*" + ], + "mac": { + "category": "public.app-category.productivity" + }, + "win": { + "target": "nsis" + } + } +} diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml new file mode 100644 index 0000000..87707db --- /dev/null +++ b/desktop/pnpm-lock.yaml @@ -0,0 +1,3079 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + electron-store: + specifier: ^8.2.0 + version: 8.2.0 + devDependencies: + electron: + specifier: ^33.2.0 + version: 33.4.11 + electron-builder: + specifier: ^25.1.8 + version: 25.1.8(electron-builder-squirrel-windows@25.1.8) + +packages: + + 7zip-bin@5.2.0: + resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==} + + '@develar/schema-utils@2.6.5': + resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} + engines: {node: '>= 8.9.0'} + + '@electron/asar@3.4.1': + resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} + engines: {node: '>=10.12.0'} + hasBin: true + + '@electron/get@2.0.3': + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + + '@electron/notarize@2.5.0': + resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==} + engines: {node: '>= 10.0.0'} + + '@electron/osx-sign@1.3.1': + resolution: {integrity: sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==} + engines: {node: '>=12.0.0'} + hasBin: true + + '@electron/rebuild@3.6.1': + resolution: {integrity: sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==} + engines: {node: '>=12.13.0'} + hasBin: true + + '@electron/universal@2.0.1': + resolution: {integrity: sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==} + engines: {node: '>=16.4'} + + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@malept/cross-spawn-promise@2.0.0': + resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} + engines: {node: '>= 12.13.0'} + + '@malept/flatpak-bundler@0.4.0': + resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} + engines: {node: '>= 10.0.0'} + + '@npmcli/fs@2.1.2': + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + '@npmcli/move-file@2.0.1': + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/fs-extra@9.0.13': + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.25': + resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} + + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + + '@types/plist@3.0.5': + resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/verror@1.10.11': + resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + app-builder-bin@5.0.0-alpha.10: + resolution: {integrity: sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==} + + app-builder-lib@25.1.8: + resolution: {integrity: sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==} + engines: {node: '>=14.0.0'} + peerDependencies: + dmg-builder: 25.1.8 + electron-builder-squirrel-windows: 25.1.8 + + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async-exit-hook@2.0.1: + resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} + engines: {node: '>=0.12.0'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + atomically@1.7.0: + resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} + engines: {node: '>=10.12.0'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird-lst@1.0.9: + resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + builder-util-runtime@9.2.10: + resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==} + engines: {node: '>=12.0.0'} + + builder-util@25.1.7: + resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==} + + cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chromium-pickle-js@0.2.0: + resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + + compare-version@0.1.2: + resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} + engines: {node: '>=0.10.0'} + + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + conf@10.2.0: + resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} + engines: {node: '>=12'} + + config-file-ts@0.2.8-rc1: + resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + + crc@3.8.0: + resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debounce-fn@4.0.0: + resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} + engines: {node: '>=10'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + dir-compare@4.2.0: + resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} + + dmg-builder@25.1.8: + resolution: {integrity: sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==} + + dmg-license@1.0.11: + resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} + engines: {node: '>=8'} + os: [darwin] + hasBin: true + + dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + + dotenv-expand@11.0.7: + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-builder-squirrel-windows@25.1.8: + resolution: {integrity: sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==} + + electron-builder@25.1.8: + resolution: {integrity: sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==} + engines: {node: '>=14.0.0'} + hasBin: true + + electron-publish@25.1.7: + resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} + + electron-store@8.2.0: + resolution: {integrity: sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==} + + electron@33.4.11: + resolution: {integrity: sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==} + engines: {node: '>= 12.20.55'} + hasBin: true + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + extsprintf@1.4.1: + resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} + engines: {'0': node >=0.6.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-corefoundation@1.1.7: + resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} + engines: {node: ^8.11.2 || >=10} + os: [darwin] + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isbinaryfile@4.0.10: + resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} + engines: {node: '>= 8.0.0'} + + isbinaryfile@5.0.7: + resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==} + engines: {node: '>= 18.0.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@7.0.3: + resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + lazy-val@1.0.5: + resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@3.1.0: + resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} + engines: {node: '>=8'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + node-abi@3.80.0: + resolution: {integrity: sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==} + engines: {node: '>=10'} + + node-addon-api@1.7.2: + resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} + + node-api-version@0.2.1: + resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} + + node-gyp@9.4.1: + resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + + nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pe-library@0.4.1: + resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} + engines: {node: '>=12', npm: '>=6'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + + plist@3.1.0: + resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} + engines: {node: '>=10.4.0'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + read-binary-file-arch@1.0.6: + resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} + hasBin: true + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resedit@1.7.2: + resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} + engines: {node: '>=12', npm: '>=6'} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sanitize-filename@1.6.3: + resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} + + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + ssri@9.0.1: + resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + stat-mode@1.0.0: + resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} + engines: {node: '>= 6'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + temp-file@3.4.0: + resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} + + tmp-promise@3.0.3: + resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + engines: {node: '>=14.14'} + + truncate-utf8-bytes@1.0.2: + resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unique-filename@2.0.1: + resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + unique-slug@3.0.0: + resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + utf8-byte-length@1.0.5: + resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + verror@1.10.1: + resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} + engines: {node: '>=0.6.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + +snapshots: + + 7zip-bin@5.2.0: {} + + '@develar/schema-utils@2.6.5': + dependencies: + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + '@electron/asar@3.4.1': + dependencies: + commander: 5.1.0 + glob: 7.2.3 + minimatch: 3.1.2 + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@electron/notarize@2.5.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + promise-retry: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@electron/osx-sign@1.3.1': + dependencies: + compare-version: 0.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + isbinaryfile: 4.0.10 + minimist: 1.2.8 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@electron/rebuild@3.6.1': + dependencies: + '@malept/cross-spawn-promise': 2.0.0 + chalk: 4.1.2 + debug: 4.4.3 + detect-libc: 2.1.2 + fs-extra: 10.1.0 + got: 11.8.6 + node-abi: 3.80.0 + node-api-version: 0.2.1 + node-gyp: 9.4.1 + ora: 5.4.1 + read-binary-file-arch: 1.0.6 + semver: 7.7.3 + tar: 6.2.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - supports-color + + '@electron/universal@2.0.1': + dependencies: + '@electron/asar': 3.4.1 + '@malept/cross-spawn-promise': 2.0.0 + debug: 4.4.3 + dir-compare: 4.2.0 + fs-extra: 11.3.2 + minimatch: 9.0.5 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@gar/promisify@1.1.3': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@malept/cross-spawn-promise@2.0.0': + dependencies: + cross-spawn: 7.0.6 + + '@malept/flatpak-bundler@0.4.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + lodash: 4.17.21 + tmp-promise: 3.0.3 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@2.1.2': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.7.3 + + '@npmcli/move-file@2.0.1': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@sindresorhus/is@4.6.0': {} + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@tootallnate/once@2.0.0': {} + + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.0.4 + '@types/keyv': 3.1.4 + '@types/node': 20.19.25 + '@types/responselike': 1.0.3 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/fs-extra@9.0.13': + dependencies: + '@types/node': 24.10.1 + + '@types/http-cache-semantics@4.0.4': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 20.19.25 + + '@types/ms@2.1.0': {} + + '@types/node@20.19.25': + dependencies: + undici-types: 6.21.0 + + '@types/node@24.10.1': + dependencies: + undici-types: 7.16.0 + + '@types/plist@3.0.5': + dependencies: + '@types/node': 24.10.1 + xmlbuilder: 15.1.1 + optional: true + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 20.19.25 + + '@types/verror@1.10.11': + optional: true + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.25 + optional: true + + '@xmldom/xmldom@0.8.11': {} + + abbrev@1.1.1: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + app-builder-bin@5.0.0-alpha.10: {} + + app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): + dependencies: + '@develar/schema-utils': 2.6.5 + '@electron/notarize': 2.5.0 + '@electron/osx-sign': 1.3.1 + '@electron/rebuild': 3.6.1 + '@electron/universal': 2.0.1 + '@malept/flatpak-bundler': 0.4.0 + '@types/fs-extra': 9.0.13 + async-exit-hook: 2.0.1 + bluebird-lst: 1.0.9 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chromium-pickle-js: 0.2.0 + config-file-ts: 0.2.8-rc1 + debug: 4.4.3 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + dotenv: 16.6.1 + dotenv-expand: 11.0.7 + ejs: 3.1.10 + electron-builder-squirrel-windows: 25.1.8(dmg-builder@25.1.8) + electron-publish: 25.1.7 + form-data: 4.0.4 + fs-extra: 10.1.0 + hosted-git-info: 4.1.0 + is-ci: 3.0.1 + isbinaryfile: 5.0.7 + js-yaml: 4.1.1 + json5: 2.2.3 + lazy-val: 1.0.5 + minimatch: 10.1.1 + resedit: 1.7.2 + sanitize-filename: 1.6.3 + semver: 7.7.3 + tar: 6.2.1 + temp-file: 3.4.0 + transitivePeerDependencies: + - bluebird + - supports-color + + aproba@2.1.0: {} + + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + argparse@2.0.1: {} + + assert-plus@1.0.0: + optional: true + + astral-regex@2.0.0: + optional: true + + async-exit-hook@2.0.1: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + atomically@1.7.0: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird-lst@1.0.9: + dependencies: + bluebird: 3.7.2 + + bluebird@3.7.2: {} + + boolean@3.2.0: + optional: true + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + buffer-crc32@0.2.13: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builder-util-runtime@9.2.10: + dependencies: + debug: 4.4.3 + sax: 1.4.3 + transitivePeerDependencies: + - supports-color + + builder-util@25.1.7: + dependencies: + 7zip-bin: 5.2.0 + '@types/debug': 4.1.12 + app-builder-bin: 5.0.0-alpha.10 + bluebird-lst: 1.0.9 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + fs-extra: 10.1.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-ci: 3.0.1 + js-yaml: 4.1.1 + source-map-support: 0.5.21 + stat-mode: 1.0.0 + temp-file: 3.4.0 + transitivePeerDependencies: + - supports-color + + cacache@16.1.3: + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.2.1 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chownr@2.0.0: {} + + chromium-pickle-js@0.2.0: {} + + ci-info@3.9.0: {} + + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + optional: true + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + clone@1.0.4: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-support@1.1.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@5.1.0: {} + + compare-version@0.1.2: {} + + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + concat-map@0.0.1: {} + + conf@10.2.0: + dependencies: + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + atomically: 1.7.0 + debounce-fn: 4.0.0 + dot-prop: 6.0.1 + env-paths: 2.2.1 + json-schema-typed: 7.0.3 + onetime: 5.1.2 + pkg-up: 3.1.0 + semver: 7.7.3 + + config-file-ts@0.2.8-rc1: + dependencies: + glob: 10.4.5 + typescript: 5.9.3 + + console-control-strings@1.1.0: {} + + core-util-is@1.0.2: + optional: true + + core-util-is@1.0.3: {} + + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + + crc@3.8.0: + dependencies: + buffer: 5.7.1 + optional: true + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debounce-fn@4.0.0: + dependencies: + mimic-fn: 3.1.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + optional: true + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + optional: true + + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + + detect-libc@2.1.2: {} + + detect-node@2.1.0: + optional: true + + dir-compare@4.2.0: + dependencies: + minimatch: 3.1.2 + p-limit: 3.1.0 + + dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + fs-extra: 10.1.0 + iconv-lite: 0.6.3 + js-yaml: 4.1.1 + optionalDependencies: + dmg-license: 1.0.11 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + dmg-license@1.0.11: + dependencies: + '@types/plist': 3.0.5 + '@types/verror': 1.10.11 + ajv: 6.12.6 + crc: 3.8.0 + iconv-corefoundation: 1.1.7 + plist: 3.1.0 + smart-buffer: 4.2.0 + verror: 1.10.1 + optional: true + + dot-prop@6.0.1: + dependencies: + is-obj: 2.0.0 + + dotenv-expand@11.0.7: + dependencies: + dotenv: 16.6.1 + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + archiver: 5.3.2 + builder-util: 25.1.7 + fs-extra: 10.1.0 + transitivePeerDependencies: + - bluebird + - dmg-builder + - supports-color + + electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + fs-extra: 10.1.0 + is-ci: 3.0.1 + lazy-val: 1.0.5 + simple-update-notifier: 2.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + electron-publish@25.1.7: + dependencies: + '@types/fs-extra': 9.0.13 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + fs-extra: 10.1.0 + lazy-val: 1.0.5 + mime: 2.6.0 + transitivePeerDependencies: + - supports-color + + electron-store@8.2.0: + dependencies: + conf: 10.2.0 + type-fest: 2.19.0 + + electron@33.4.11: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 20.19.25 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: {} + + err-code@2.0.3: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es6-error@4.1.1: + optional: true + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: + optional: true + + exponential-backoff@3.1.3: {} + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + extsprintf@1.4.1: + optional: true + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-uri@3.1.0: {} + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs-constants@1.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + function-bind@1.1.2: {} + + gauge@4.0.4: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.7.3 + serialize-error: 7.0.1 + optional: true + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + optional: true + + gopd@1.2.0: {} + + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + optional: true + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + http-cache-semantics@4.2.0: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-corefoundation@1.1.7: + dependencies: + cli-truncate: 2.1.0 + node-addon-api: 1.7.2 + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + infer-owner@1.0.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ip-address@10.1.0: {} + + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 + + is-fullwidth-code-point@3.0.0: {} + + is-interactive@1.0.0: {} + + is-lambda@1.0.1: {} + + is-obj@2.0.0: {} + + is-unicode-supported@0.1.0: {} + + isarray@1.0.0: {} + + isbinaryfile@4.0.10: {} + + isbinaryfile@5.0.7: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@7.0.3: {} + + json-stringify-safe@5.0.1: + optional: true + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + lazy-val@1.0.5: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.flatten@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.union@4.6.0: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + lowercase-keys@2.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-cache@7.18.3: {} + + make-fetch-happen@10.2.1: + dependencies: + agentkeepalive: 4.6.0 + cacache: 16.1.3 + http-cache-semantics: 4.2.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.4 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-fn@3.1.0: {} + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-fetch@2.1.2: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + + ms@2.1.3: {} + + negotiator@0.6.4: {} + + node-abi@3.80.0: + dependencies: + semver: 7.7.3 + + node-addon-api@1.7.2: + optional: true + + node-api-version@0.2.1: + dependencies: + semver: 7.7.3 + + node-gyp@9.4.1: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.7.3 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + + nopt@6.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + + object-keys@1.1.1: + optional: true + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + p-cancelable@2.1.1: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + path-exists@3.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pe-library@0.4.1: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + + plist@3.1.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + process-nextick-args@2.0.1: {} + + progress@2.0.3: {} + + promise-inflight@1.0.1: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + quick-lru@5.1.1: {} + + read-binary-file-arch@1.0.6: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resedit@1.7.2: + dependencies: + pe-library: 0.4.1 + + resolve-alpn@1.2.1: {} + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + retry@0.12.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sanitize-filename@1.6.3: + dependencies: + truncate-utf8-bytes: 1.0.2 + + sax@1.4.3: {} + + semver-compare@1.0.0: + optional: true + + semver@6.3.1: {} + + semver@7.7.3: {} + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + + set-blocking@2.0.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.3 + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + optional: true + + smart-buffer@4.2.0: {} + + socks-proxy-agent@7.0.0: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.1.3: + optional: true + + ssri@9.0.1: + dependencies: + minipass: 3.3.6 + + stat-mode@1.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + sumchecker@3.0.1: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + temp-file@3.4.0: + dependencies: + async-exit-hook: 2.0.1 + fs-extra: 10.1.0 + + tmp-promise@3.0.3: + dependencies: + tmp: 0.2.5 + + tmp@0.2.5: {} + + truncate-utf8-bytes@1.0.2: + dependencies: + utf8-byte-length: 1.0.5 + + type-fest@0.13.1: + optional: true + + type-fest@2.19.0: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + undici-types@7.16.0: {} + + unique-filename@2.0.1: + dependencies: + unique-slug: 3.0.0 + + unique-slug@3.0.0: + dependencies: + imurmurhash: 0.1.4 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + utf8-byte-length@1.0.5: {} + + util-deprecate@1.0.2: {} + + verror@1.10.1: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.4.1 + optional: true + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + xmlbuilder@15.1.1: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} + + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 diff --git a/desktop/pnpm-workspace.yaml b/desktop/pnpm-workspace.yaml new file mode 100644 index 0000000..5df7f4a --- /dev/null +++ b/desktop/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +ignoredBuiltDependencies: + - electron diff --git a/desktop/src/main.js b/desktop/src/main.js new file mode 100644 index 0000000..eb8a1da --- /dev/null +++ b/desktop/src/main.js @@ -0,0 +1,196 @@ +const { app, BrowserWindow, globalShortcut, ipcMain } = require('electron'); +const path = require('path'); + +// 主窗口实例 +let mainWindow; +let hudWindow; + +function createWindow() { + // 创建浏览器窗口 + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 900, + minHeight: 600, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + enableRemoteModule: false, + preload: path.join(__dirname, 'preload.js') + }, + titleBarStyle: 'default', + show: false, // 先不显示,准备好后再显示 + title: 'LiveGalGame Desktop' + }); + + // 加载index.html + mainWindow.loadFile(path.join(__dirname, 'renderer/index.html')); + + // 窗口准备就绪后显示 + mainWindow.once('ready-to-show', () => { + mainWindow.show(); + // 开发环境自动打开开发者工具 + if (process.env.NODE_ENV === 'development') { + mainWindow.webContents.openDevTools(); + } + }); + + // 窗口关闭事件 + mainWindow.on('closed', () => { + mainWindow = null; + // 主窗口关闭时,也关闭HUD + if (hudWindow) { + hudWindow.close(); + } + }); + + // 监听来自渲染进程的IPC消息 + setupIPC(); +} + +// 设置IPC通信 +function setupIPC() { + // 显示HUD + ipcMain.on('show-hud', () => { + if (!hudWindow) { + createHUDWindow(); + } else { + hudWindow.show(); + } + console.log('HUD显示'); + }); + + // 隐藏HUD + ipcMain.on('hide-hud', () => { + if (hudWindow) { + hudWindow.hide(); + console.log('HUD隐藏'); + } + }); + + // 关闭HUD + ipcMain.on('close-hud', () => { + if (hudWindow) { + hudWindow.close(); + hudWindow = null; + console.log('HUD关闭'); + } + }); + + console.log('IPC通信已设置'); +} + +// 创建HUD窗口 +function createHUDWindow() { + try { + const { screen } = require('electron'); + const primaryDisplay = screen.getPrimaryDisplay(); + const { width, height } = primaryDisplay.workAreaSize; + + console.log(`Creating HUD window at position: ${width - 420}, ${height - 320}`); + + hudWindow = new BrowserWindow({ + width: 400, + height: 300, + x: width - 420, + y: height - 320, + frame: false, + transparent: true, + alwaysOnTop: true, + skipTaskbar: true, + resizable: false, + show: false, // 先不显示,等ready后再显示 + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + enableRemoteModule: false, + preload: path.join(__dirname, 'preload.js') + }, + title: 'LiveGalGame HUD' + }); + + // 加载HUD页面 + hudWindow.loadFile(path.join(__dirname, 'renderer/hud.html')); + + // 页面加载完成后再显示 + hudWindow.once('ready-to-show', () => { + console.log('HUD window ready to show'); + hudWindow.show(); + }); + + // 页面加载错误处理 + hudWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + console.error('HUD failed to load:', errorCode, errorDescription); + }); + + // HUD关闭事件 + hudWindow.on('closed', () => { + console.log('HUD window closed'); + hudWindow = null; + // 通知主窗口HUD已关闭 + if (mainWindow) { + mainWindow.webContents.send('hud-closed'); + } + }); + + console.log('HUD窗口创建成功'); + } catch (error) { + console.error('Failed to create HUD window:', error); + } +} + +// 注册全局快捷键 +function registerGlobalShortcuts() { + // Ctrl+R 刷新 + globalShortcut.register('CommandOrControl+R', () => { + if (mainWindow) { + mainWindow.reload(); + console.log('窗口已刷新'); + } + }); + + // Ctrl+Shift+I 打开开发者工具 + globalShortcut.register('CommandOrControl+Shift+I', () => { + if (mainWindow) { + mainWindow.webContents.toggleDevTools(); + console.log('开发者工具已切换'); + } + }); + + // ESC 键最小化HUD(后续实现) + globalShortcut.register('Escape', () => { + console.log('ESC pressed - will minimize HUD later'); + }); + + console.log('全局快捷键已注册'); +} + +// 应用准备就绪 +app.whenReady().then(() => { + createWindow(); + // createHUDWindow(); // 暂时不自动创建HUD,等待用户触发 + registerGlobalShortcuts(); + + // macOS上激活应用时创建窗口 + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); + +// 应用退出前清理 +app.on('will-quit', () => { + // 注销所有全局快捷键 + globalShortcut.unregisterAll(); + console.log('全局快捷键已注销'); +}); + +// 所有窗口关闭时退出应用(除了macOS) +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +console.log('LiveGalGame Desktop 启动成功!'); diff --git a/desktop/src/preload.js b/desktop/src/preload.js new file mode 100644 index 0000000..573b314 --- /dev/null +++ b/desktop/src/preload.js @@ -0,0 +1,28 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +// 暴露安全的API给渲染进程 +contextBridge.exposeInMainWorld('electronAPI', { + // 平台信息 + platform: process.platform, + + // IPC通信 + send: (channel, data) => ipcRenderer.send(channel, data), + on: (channel, callback) => ipcRenderer.on(channel, (event, ...args) => callback(...args)), + once: (channel, callback) => ipcRenderer.once(channel, (event, ...args) => callback(...args)), + removeListener: (channel, callback) => ipcRenderer.removeListener(channel, callback), + + // 日志 + log: (message) => ipcRenderer.send('log', message), + + // HUD控制 + showHUD: () => ipcRenderer.send('show-hud'), + hideHUD: () => ipcRenderer.send('hide-hud'), + closeHUD: () => ipcRenderer.send('close-hud') +}); + +// 监听主进程消息 +ipcRenderer.on('window-focused', () => { + console.log('Window focused'); +}); + +console.log('Preload script loaded successfully'); diff --git a/desktop/src/renderer/conversation-editor.html b/desktop/src/renderer/conversation-editor.html new file mode 100644 index 0000000..5248a41 --- /dev/null +++ b/desktop/src/renderer/conversation-editor.html @@ -0,0 +1,283 @@ + + + + + + LiveGalGame - 对话编辑器 + + + + + + + + + + + + + + diff --git a/desktop/src/renderer/hud.html b/desktop/src/renderer/hud.html new file mode 100644 index 0000000..243b633 --- /dev/null +++ b/desktop/src/renderer/hud.html @@ -0,0 +1,253 @@ + + + + + + LiveGalGame HUD + + + + +
+ +
+
+ + LiveGalGame 助手 +
+
+ + +
+
+ + +
+
实时转录
+ +
+
对方
+
+
+ 哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。 +
+
+
+ +
+
+
+
+ 听起来真不错!也许我们可以一起去? +
+
+
+
+ + +
+
AI 建议
+
+
+
+ 提议具体地点 + ❤️ +15 +
+

"我知道附近有个很棒的公园,樱花特别美,要不要去那里?"

+
+ 主动 + 体贴 +
+
+ +
+
+ 表达期待 + ❤️ +10 +
+

"太好了!我一直想和你一起去散步呢。"

+
+ 情感 + 真诚 +
+
+
+
+
+ + + + diff --git a/desktop/src/renderer/index.html b/desktop/src/renderer/index.html new file mode 100644 index 0000000..7b33693 --- /dev/null +++ b/desktop/src/renderer/index.html @@ -0,0 +1,254 @@ + + + + + + LiveGalGame - 总览 + + + + + + + + + + +
+ + + + +
+
+ +
+

欢迎回来!

+

这是您的对话项目快照。

+
+ + +
+
+
+ groups +
+
+

攻略对象

+

12

+
+
+
+
+ chat_bubble +
+
+

对话

+

89

+
+
+
+
+ account_tree +
+
+

分支

+

256

+
+
+
+
+ flag +
+
+

故事标记

+

42

+
+
+
+ + +
+
+

最近对话

+
+ + +
+
+

"我"与攻略对象的聊天记录摘要。

+
+ + +
+
+
+
+

第1章:命运的相遇

+
+
+
+
+
+

优衣在樱花节上初次遇见健司的开场场景。确立了他们最初的动态关系。

+
+
+

最后编辑:2天前

+ +
+
+ +
+
+
+

咖啡馆的误会

+
+
+
+
+
+

一个关键的冲突场景。健司看到优衣和她的童年朋友明说话,导致了一个主要的情节点。

+
+
+

最后编辑:5天前

+ +
+
+ +
+
+
+

图书馆的告白

+
+
+
+
+

一个感人的独白,优衣在学校图书馆学习到很晚时向玩家表白了她的感情。

+
+
+

最后编辑:1周前

+ +
+
+ + + + add_circle +

创建新对话

+

从头开始一段新对话。

+
+
+
+
+
+ + + + diff --git a/desktop/src/renderer/js/renderer.js b/desktop/src/renderer/js/renderer.js new file mode 100644 index 0000000..eeb239c --- /dev/null +++ b/desktop/src/renderer/js/renderer.js @@ -0,0 +1,405 @@ +// 渲染进程脚本 +console.log('Renderer process loaded'); + +// 当前选中的对话 +let currentConversation = null; + +// 对话数据(示例) +const conversations = { + miyu: { + id: 'miyu', + name: 'Miyu', + avatarColor: '#ff6b6b', + tags: ['愉快', '日常'], + messages: [ + { + id: 1, + sender: 'other', + text: '哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。', + timestamp: '2025-11-13 10:30:00' + }, + { + id: 2, + sender: 'user', + text: '听起来真不错!也许我们可以一起去?', + timestamp: '2025-11-13 10:31:00' + } + ] + }, + akira: { + id: 'akira', + name: 'Akira', + avatarColor: '#4ecdc4', + tags: ['紧张', '关键剧情'], + messages: [ + { + id: 1, + sender: 'other', + text: '别忘了我们的约定!', + timestamp: '2025-11-13 09:15:00' + } + ] + }, + hana: { + id: 'hana', + name: 'Hana', + avatarColor: '#ffe66d', + tags: ['愉快', '日常'], + messages: [ + { + id: 1, + sender: 'other', + text: '我今天发现了一家超可爱的小咖啡馆。', + timestamp: '2025-11-13 08:45:00' + } + ] + } +}; + +// DOM加载完成后执行 +document.addEventListener('DOMContentLoaded', () => { + console.log('DOM loaded'); + initializeApp(); +}); + +// 初始化应用 +function initializeApp() { + setupEventListeners(); + setupNavigation(); + setupHUDControl(); + console.log('App initialized'); +} + +// 设置HUD控制 +function setupHUDControl() { + const showHUDBtn = document.getElementById('btn-show-hud'); + if (showHUDBtn) { + showHUDBtn.addEventListener('click', () => { + console.log('Show HUD button clicked'); + if (window.electronAPI && window.electronAPI.showHUD) { + window.electronAPI.showHUD(); + alert('HUD浮窗已打开(如果未显示,请检查日志)'); + } else { + console.error('electronAPI.showHUD not available'); + alert('electronAPI.showHUD 不可用'); + } + }); + } + + console.log('HUD control setup'); +} + +// 设置导航事件 +function setupNavigation() { + const navItems = document.querySelectorAll('.nav-item'); + navItems.forEach(item => { + item.addEventListener('click', (e) => { + const href = item.getAttribute('href'); + + // 如果是外部链接(不是 #),允许默认跳转 + if (href && href !== '#' && !href.startsWith('#')) { + console.log(`Navigating to: ${href}`); + return; // 允许默认行为 + } + + // 阻止默认行为,处理内部导航 + e.preventDefault(); + const page = item.dataset.page; + + // 更新导航激活状态 + navItems.forEach(nav => nav.classList.remove('bg-white/20')); + navItems.forEach(nav => nav.classList.add('text-white/80')); + item.classList.add('bg-white/20'); + item.classList.remove('text-white/80'); + + console.log(`Navigate to: ${page}`); + + // 显示即将推出的提示 + if (page === 'characters') { + alert('攻略对象管理功能即将推出!'); + } else if (page === 'llm') { + alert('LLM配置功能即将推出!'); + } else if (page === 'settings') { + alert('设置功能即将推出!'); + } + }); + }); +} + +// 设置事件监听器 +function setupEventListeners() { + // 对话列表点击事件 + const conversationItems = document.querySelectorAll('.conversation-item'); + conversationItems.forEach(item => { + item.addEventListener('click', () => { + const conversationId = item.dataset.conversationId; + selectConversation(conversationId); + }); + }); + + // "新对话"按钮 + const newConversationBtns = document.querySelectorAll('.btn-create-conversation'); + newConversationBtns.forEach(btn => { + btn.addEventListener('click', createNewConversation); + }); + + // "添加消息"按钮 + const addMessageBtn = document.querySelector('.btn-add-message'); + if (addMessageBtn) { + addMessageBtn.addEventListener('click', addNewMessage); + } + + // "保存对话"按钮 + const saveBtn = document.querySelector('.btn-save-conversation'); + if (saveBtn) { + saveBtn.addEventListener('click', saveConversation); + } + + console.log('Event listeners attached'); +} + +// 选择对话 +function selectConversation(conversationId) { + currentConversation = conversationId; + + // 更新UI + updateConversationListUI(conversationId); + showConversation(conversationId); + + console.log(`Selected conversation: ${conversationId}`); +} + +// 更新对话列表UI +function updateConversationListUI(selectedId) { + const items = document.querySelectorAll('.conversation-item'); + items.forEach(item => { + item.classList.remove('bg-surface-light', 'dark:bg-surface-dark'); + item.classList.add('hover:bg-surface-light', 'dark:hover:bg-surface-dark'); + }); + + const selectedItem = document.querySelector(`[data-conversation-id="${selectedId}"]`); + if (selectedItem) { + selectedItem.classList.remove('hover:bg-surface-light', 'dark:hover:bg-surface-dark'); + selectedItem.classList.add('bg-surface-light', 'dark:bg-surface-dark'); + } +} + +// 显示对话详情 +function showConversation(conversationId) { + const conversation = conversations[conversationId]; + if (!conversation) return; + + // 隐藏欢迎页,显示对话页 + document.getElementById('welcome-page').classList.add('hidden'); + document.getElementById('conversation-page').classList.remove('hidden'); + + // 更新对话头部信息 + updateConversationHeader(conversation); + + // 渲染消息列表 + renderMessages(conversation.messages); + + // 更新AI洞察 + updateAIInsights(conversationId); + + console.log(`Showing conversation: ${conversation.name}`); +} + +// 更新对话头部 +function updateConversationHeader(conversation) { + const avatar = document.querySelector('.conversation-avatar'); + const title = document.querySelector('.conversation-title'); + const tags = document.querySelector('.conversation-tags'); + + if (avatar) avatar.style.backgroundColor = conversation.avatarColor; + if (title) title.innerHTML = `与 ${conversation.name} 的对话*`; + + if (tags) { + tags.innerHTML = conversation.tags.map(tag => ` + + mood${tag} + + `).join(''); + } +} + +// 渲染消息列表 +function renderMessages(messages) { + const container = document.querySelector('.conversation-messages'); + if (!container) return; + + container.innerHTML = messages.map(msg => { + const isUser = msg.sender === 'user'; + return ` +
+ ${!isUser ? ` +
+ ` : ''} +
+

+ ${isUser ? '我' : '攻略对象'} +

+
+

+ ${msg.text} +

+
+ +
+
+
+
+ `; + }).join(''); +} + +// 创建新对话 +function createNewConversation() { + console.log('Create new conversation'); + alert('新对话功能即将推出!'); +} + +// 添加新消息 +function addNewMessage() { + console.log('Add new message'); + alert('添加消息功能即将推出!'); +} + +// 保存对话 +function saveConversation() { + console.log('Save conversation'); + alert('对话已保存!'); +} + +// 移除标签 +window.removeTag = function(button) { + const tagElement = button.parentElement; + tagElement.remove(); + console.log('Tag removed'); +} + +// 添加标签 +function addTag(tagName) { + const selectedTagsContainer = document.getElementById('selected-tags'); + + // 检查是否已存在 + const existingTags = Array.from(selectedTagsContainer.children); + const exists = existingTags.some(tag => tag.textContent.includes(tagName)); + if (exists) { + console.log(`Tag already exists: ${tagName}`); + return; + } + + // 创建新标签 + const tagElement = document.createElement('span'); + tagElement.className = 'inline-flex items-center gap-1 bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300 text-xs font-semibold px-2.5 py-1 rounded-full'; + tagElement.innerHTML = ` + ${tagName} + + `; + + selectedTagsContainer.appendChild(tagElement); + console.log(`Tag added: ${tagName}`); +} + +// 设置AI面板事件监听器 +function setupAIPanelEvents() { + // 建议标签点击事件 + const suggestedTags = document.querySelectorAll('.suggested-tag'); + suggestedTags.forEach(btn => { + btn.addEventListener('click', () => { + const tagName = btn.dataset.tag; + addTag(tagName); + }); + }); + + // 更新消息按钮 + const updateMessageBtn = document.getElementById('btn-update-message'); + if (updateMessageBtn) { + updateMessageBtn.addEventListener('click', () => { + const messageContent = document.getElementById('message-content').value; + console.log('Update message:', messageContent); + alert('消息已更新!'); + }); + } + + console.log('AI panel events attached'); +} + +// 更新对话时更新AI分析 +function updateAIInsights(conversationId) { + const insights = { + miyu: [ + { + icon: 'auto_awesome', + color: 'text-yellow-600 dark:text-yellow-400', + text: '这个回应非常有效。通过提出一起散步的建议,你主动推进了关系。这是一个关键的积极转折点。' + }, + { + icon: 'thumb_up', + color: 'text-green-600 dark:text-green-400', + text: '建议:继续保持主动,可以提出一个具体的见面地点,展现你的体贴。' + } + ], + akira: [ + { + icon: 'warning', + color: 'text-orange-600 dark:text-orange-400', + text: '对方提到了约定,这是一个重要的承诺节点,需要认真对待。' + }, + { + icon: 'help', + color: 'text-blue-600 dark:text-blue-400', + text: '建议:确认具体约定内容,展现你的可靠性和记忆力。' + } + ], + hana: [ + { + icon: 'auto_awesome', + color: 'text-yellow-600 dark:text-yellow-400', + text: '对方在分享日常生活,这是建立亲密感的好机会。' + }, + { + icon: 'favorite', + color: 'text-pink-600 dark:text-pink-400', + text: '建议:表现出对对方生活的兴趣,可以询问咖啡馆的细节。' + } + ] + }; + + const conversationInsights = insights[conversationId] || []; + const container = document.getElementById('ai-insights'); + + if (container && conversationInsights.length > 0) { + const insightsHTML = conversationInsights.map(insight => ` +
+ ${insight.icon} +

${insight.text}

+
+ `).join(''); + + container.innerHTML = ` +

AI 洞察

+
+ ${insightsHTML} +
+ `; + } +} + +// 暴露全局函数供HTML调用 +window.LiveGalGame = { + toggleDarkMode: () => { + document.documentElement.classList.toggle('dark'); + } +}; + +// 初始化时设置AI面板事件 +setupAIPanelEvents(); From de84d56a95d3bfaf3580be471b589f3ff24a262e Mon Sep 17 00:00:00 2001 From: chenspeculation Date: Thu, 13 Nov 2025 00:22:49 +0800 Subject: [PATCH 027/147] feat: complete HUD window functionality and add new settings page - Finalized HUD window features including drag-and-drop functionality and resizing capabilities. - Updated main process to handle HUD drag events and adjusted window dimensions. - Introduced a new settings page with microphone permissions, audio testing components, and API configuration. - Enhanced existing HTML files for better user experience and navigation. - Added new character and conversation editor pages to improve application structure. --- desktop/PROMPT_SUMMARY.md | 26 +- .../images/llm\351\205\215\347\275\256.html" | 210 +++++++ .../images/\344\270\273\351\241\265.html" | 230 ++++++++ ...\350\257\235\351\241\265\351\235\242.html" | 251 ++++++++ ...\345\201\234\345\212\251\346\211\213.html" | 182 ++++++ desktop/src/main.js | 66 ++- desktop/src/preload.js | 7 +- desktop/src/renderer/characters.html | 290 ++++++++++ desktop/src/renderer/conversation-editor.html | 4 +- desktop/src/renderer/hud.html | 216 +++++-- desktop/src/renderer/index.html | 8 +- desktop/src/renderer/settings.html | 546 ++++++++++++++++++ 12 files changed, 1961 insertions(+), 75 deletions(-) create mode 100644 "desktop/spec/images/llm\351\205\215\347\275\256.html" create mode 100644 "desktop/spec/images/\344\270\273\351\241\265.html" create mode 100644 "desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" create mode 100644 "desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" create mode 100644 desktop/src/renderer/characters.html create mode 100644 desktop/src/renderer/settings.html diff --git a/desktop/PROMPT_SUMMARY.md b/desktop/PROMPT_SUMMARY.md index d8b1763..d77199e 100644 --- a/desktop/PROMPT_SUMMARY.md +++ b/desktop/PROMPT_SUMMARY.md @@ -16,20 +16,21 @@ - [x] AI分析面板 (动态洞察 + 标签管理) - [x] **页面跳转系统** (总览 ↔ 对话编辑器) -### ✅ 阶段 2: HUD浮窗基础 (50%) +### ✅ 阶段 2: HUD浮窗基础 (100%) - [x] HUD窗口创建 (无边框、透明、右下角) - [x] HUD UI基础 (双通道转录 + AI建议卡片) - [x] 主窗口与HUD通信 (IPC机制) -- [ ] **待完成**: 从主窗口触发HUD显示 -- [ ] **待完成**: HUD拖拽移动 -- [ ] **待完成**: 鼠标穿透切换 +- [x] 从主窗口触发HUD显示 +- [x] HUD拖拽移动 +- [x] 鼠标穿透切换 (通过窗口边缘调整大小) +- [x] 窗口大小调节 ## 核心架构说明 ### 主进程 (main.js) - `mainWindow`: 主窗口 (1200x800) -- `hudWindow`: HUD浮窗 (400x300, 右下角) -- IPC通信: `show-hud`, `hide-hud`, `close-hud` +- `hudWindow`: HUD浮窗 (520x600, 右下角, 可拖拽、可调整大小) +- IPC通信: `show-hud`, `hide-hud`, `close-hud`, `start-hud-drag`, `update-hud-drag`, `end-hud-drag` ### 渲染进程 - **index.html**: 总览页 @@ -40,15 +41,24 @@ - 三栏布局 (对话列表/详情/AI分析) - 对话切换功能 - **hud.html**: HUD浮窗 - - 双通道转录 (对方/我) + - 聊天式消息气泡 (左对齐/右对齐) - AI建议卡片 - - 最小化/关闭按钮 + - 拖拽移动功能 + - 关闭按钮 +- **settings.html**: 设置页面 (NEW) + - 麦克风权限请求UI + - 音频测试组件 + - 音量检测和波形可视化 + - API配置界面 ### 预加载脚本 (preload.js) 暴露API: - `window.electronAPI.showHUD()` - `window.electronAPI.hideHUD()` - `window.electronAPI.closeHUD()` +- `window.electronAPI.startHUDDrag(pos)` +- `window.electronAPI.updateHUDDrag(pos)` +- `window.electronAPI.endHUDDrag()` ## 当前问题 diff --git "a/desktop/spec/images/llm\351\205\215\347\275\256.html" "b/desktop/spec/images/llm\351\205\215\347\275\256.html" new file mode 100644 index 0000000..ee7bbdb --- /dev/null +++ "b/desktop/spec/images/llm\351\205\215\347\275\256.html" @@ -0,0 +1,210 @@ + + + + +LiveGalGame - LLM 配置 + + + + + + + + + + +
+ +
+
+
+
+

LLM 配置

+

管理您的 AI 模型配置,添加新模型并设置默认值。

+
+ +
+
+
+
+
+star +
+
+

GPT-4o (OpenAI)

+
+
+

已激活

+
+

gpt-4o

+
+
+
+ + + +
+
+
+
+
+model_training +
+
+

Claude 3 Opus (Anthropic)

+
+
+

未激活

+
+

claude-3-opus-20240229

+
+
+
+ + + + +
+
+
+
+
+dns +
+
+

本地 Llama 3 (Oobabooga)

+
+
+

连接错误

+
+

本地 Oobabooga

+
+
+
+ + + + +
+
+
+add_circle +

尚未配置模型

+

通过添加您的第一个 AI 模型配置开始。连接到 OpenAI、Anthropic 或本地实例。

+ +
+
+
+
+
+ \ No newline at end of file diff --git "a/desktop/spec/images/\344\270\273\351\241\265.html" "b/desktop/spec/images/\344\270\273\351\241\265.html" new file mode 100644 index 0000000..b30728c --- /dev/null +++ "b/desktop/spec/images/\344\270\273\351\241\265.html" @@ -0,0 +1,230 @@ + + + + +LiveGalGame - 总览 + + + + + + + + + + +
+ +
+
+
+

欢迎回来!

+

这是您的对话项目快照。

+
+
+
+
+groups +
+
+

攻略对象

+

12

+
+
+
+
+chat_bubble +
+
+

对话

+

89

+
+
+
+
+account_tree +
+
+

分支

+

256

+
+
+
+
+flag +
+
+

故事标记

+

42

+
+
+
+
+
+

最近对话

+
+ + +
+
+

“我”与攻略对象的聊天记录摘要。

+
+
+
+
+
+

第1章:命运的相遇

+
+
+
+
+
+

优衣在樱花节上初次遇见健司的开场场景。确立了他们最初的动态关系。

+
+
+

最后编辑:2天前

+ +
+
+
+
+
+

咖啡馆的误会

+
+
+
+
+
+

一个关键的冲突场景。健司看到优衣和她的童年朋友明说话,导致了一个主要的情节点。

+
+
+

最后编辑:5天前

+ +
+
+
+
+
+

图书馆的告白

+
+
+
+
+

一个感人的独白,优衣在学校图书馆学习到很晚时向玩家表白了她的感情。

+
+
+

最后编辑:1周前

+ +
+
+
+add_circle +

创建新对话

+

从头开始一段新对话。

+
+
+
+
+
+ \ No newline at end of file diff --git "a/desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" "b/desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" new file mode 100644 index 0000000..d4ba09b --- /dev/null +++ "b/desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" @@ -0,0 +1,251 @@ + + + + +LiveGalGame - 对话编辑器 + + + + + + + + + + +
+ +
+
+
+
+
+

与 Miyu 的对话*

+
+mood愉快 +local_florist日常 +
+
+
+
+ + + +
+
+
+
+
+
+

攻略对象

+
+

哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。

+
+ +
+
+
+
+
+
+

+
+

听起来真不错!也许我们可以一起去?

+
+ +
+
+
+
+
+
+
+
+

攻略对象

+
+

+我当然愿意! 我们在哪里见面好呢? +

+
+ +
+
+
+
+
+
+auto_awesome +

AI分析:积极回应!对方接受了你的邀请,表明好感度提升。

+
+
+mood愉快 +favorite心动 +
+
+
+
+ +
+
+
+ +
+ \ No newline at end of file diff --git "a/desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" "b/desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" new file mode 100644 index 0000000..671259b --- /dev/null +++ "b/desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" @@ -0,0 +1,182 @@ + + + + +LiveGalGame AI 助手 + + + + + + + + + + +
+
+
+

对话: Evelyn

+
+ + +
+
+
+
+
+

没想到今晚会在这里见到你。

+
+
+
+
+

这个城市充满了惊喜,不是吗?

+
+
+
+
+

也可以这么说。我听说你在调查“猩红集团”的案子。

+
+
+
+
+

只是跟进几条线索,还没什么实质进展。

+
+
+
+
+

AI 推荐:

+
+
+

“你似乎对此了解很多。愿意分享一下吗?”

+
+
积极
+
最佳匹配
+
+
+
+

“这是一条危险的路。我建议你远离。”

+
+
中性
+
创意
+
+
+
+
+
+
+

AI 思考中...

+
+
+
+
+ + + + +
+sentiment_very_satisfied +
+
+
+
+ + + +
+
+
+
+
+

AI 选项

+ +
+
+
+

重要时刻

+
+
+

提及“猩红集团”

+

Evelyn似乎在调查此案。

+
+
+

意外的相遇

+

对话开头的关键转折点。

+
+
+
+
+ + +
+
+ + +
+
+自动建议触发 + +
+
+紧凑显示模式 + +
+
+
+
+
+
+ + + + +
+sentiment_very_dissatisfied +
+
+
+
+ \ No newline at end of file diff --git a/desktop/src/main.js b/desktop/src/main.js index eb8a1da..6fef3f2 100644 --- a/desktop/src/main.js +++ b/desktop/src/main.js @@ -77,6 +77,51 @@ function setupIPC() { } }); + // HUD拖拽相关变量 + let dragStartPos = { x: 0, y: 0 }; + let dragWindowBounds = { x: 0, y: 0, width: 0, height: 0 }; + let isHUDDragging = false; + + // 开始拖拽HUD + ipcMain.on('start-hud-drag', (event, pos) => { + if (!hudWindow) return; + isHUDDragging = true; + dragStartPos = pos; + // 获取窗口的完整边界信息(位置和大小) + const bounds = hudWindow.getBounds(); + dragWindowBounds = { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height + }; + console.log('HUD拖拽开始,窗口边界:', dragWindowBounds); + }); + + // 更新HUD拖拽位置 + // 重要:使用setBounds同时设置位置和大小,避免高DPI缩放时窗口无限放大 + // 参考:https://zhuanlan.zhihu.com/p/112564936 + ipcMain.on('update-hud-drag', (event, pos) => { + if (!hudWindow || !isHUDDragging) return; + const deltaX = pos.x - dragStartPos.x; + const deltaY = pos.y - dragStartPos.y; + const newX = dragWindowBounds.x + deltaX; + const newY = dragWindowBounds.y + deltaY; + // 必须使用setBounds同时设置位置和大小,不能只用setPosition + hudWindow.setBounds({ + x: newX, + y: newY, + width: dragWindowBounds.width, + height: dragWindowBounds.height + }); + }); + + // 结束HUD拖拽 + ipcMain.on('end-hud-drag', () => { + isHUDDragging = false; + console.log('HUD拖拽结束'); + }); + console.log('IPC通信已设置'); } @@ -87,18 +132,22 @@ function createHUDWindow() { const primaryDisplay = screen.getPrimaryDisplay(); const { width, height } = primaryDisplay.workAreaSize; - console.log(`Creating HUD window at position: ${width - 420}, ${height - 320}`); + console.log(`Creating HUD window at position: ${width - 540}, ${height - 620}`); hudWindow = new BrowserWindow({ - width: 400, - height: 300, - x: width - 420, - y: height - 320, + width: 520, + height: 600, + minWidth: 400, + minHeight: 300, + maxWidth: 1200, + maxHeight: 1000, + x: width - 540, + y: height - 620, frame: false, transparent: true, alwaysOnTop: true, skipTaskbar: true, - resizable: false, + resizable: true, // 允许调整大小 show: false, // 先不显示,等ready后再显示 webPreferences: { nodeIntegration: false, @@ -109,12 +158,17 @@ function createHUDWindow() { title: 'LiveGalGame HUD' }); + // 确保窗口可以调整大小(显式设置) + hudWindow.setResizable(true); + // 加载HUD页面 hudWindow.loadFile(path.join(__dirname, 'renderer/hud.html')); // 页面加载完成后再显示 hudWindow.once('ready-to-show', () => { console.log('HUD window ready to show'); + // 再次确认窗口可以调整大小 + hudWindow.setResizable(true); hudWindow.show(); }); diff --git a/desktop/src/preload.js b/desktop/src/preload.js index 573b314..4133428 100644 --- a/desktop/src/preload.js +++ b/desktop/src/preload.js @@ -17,7 +17,12 @@ contextBridge.exposeInMainWorld('electronAPI', { // HUD控制 showHUD: () => ipcRenderer.send('show-hud'), hideHUD: () => ipcRenderer.send('hide-hud'), - closeHUD: () => ipcRenderer.send('close-hud') + closeHUD: () => ipcRenderer.send('close-hud'), + + // HUD拖拽 + startHUDDrag: (pos) => ipcRenderer.send('start-hud-drag', pos), + updateHUDDrag: (pos) => ipcRenderer.send('update-hud-drag', pos), + endHUDDrag: () => ipcRenderer.send('end-hud-drag') }); // 监听主进程消息 diff --git a/desktop/src/renderer/characters.html b/desktop/src/renderer/characters.html new file mode 100644 index 0000000..a56bb51 --- /dev/null +++ b/desktop/src/renderer/characters.html @@ -0,0 +1,290 @@ + + + + + + LiveGalGame - 攻略对象 + + + + + + + + + +
+ + + + +
+
+ +
+
+

攻略对象

+
+

管理与各个角色的关系和档案

+
+ + +
+
+
+ 总计攻略对象 + groups +
+
3
+
+ +
+
+ 活跃对话 + chat +
+
5
+
+ +
+
+ 平均好感度 + favorite +
+
68
+
+
+ + +
+ +
+
+
M
+
+

Miyu

+

青梅竹马

+
+
+ +
+
+ 好感度 + 75% +
+
+
+
+
+ +
+
+ 最近对话 + 2小时前 +
+
+ 对话次数 + 12 +
+
+ +
+

关键词

+
+ 活泼 + 可爱 + 关心 +
+
+ +
+ + +
+
+ + +
+
+
A
+
+

Akira

+

学生会长

+
+
+ +
+
+ 好感度 + 60% +
+
+
+
+
+ +
+
+ 最近对话 + 1天前 +
+
+ 对话次数 + 8 +
+
+ +
+

关键词

+
+ 认真 + 负责 + 温柔 +
+
+ +
+ + +
+
+ + +
+
+
H
+
+

Hana

+

图书馆管理员

+
+
+ +
+
+ 好感度 + 45% +
+
+
+
+
+ +
+
+ 最近对话 + 3天前 +
+
+ 对话次数 + 5 +
+
+ +
+

关键词

+
+ 文静 + 书虫 + 害羞 +
+
+ +
+ + +
+
+
+
+
+
+ + + + diff --git a/desktop/src/renderer/conversation-editor.html b/desktop/src/renderer/conversation-editor.html index 5248a41..045bc75 100644 --- a/desktop/src/renderer/conversation-editor.html +++ b/desktop/src/renderer/conversation-editor.html @@ -3,7 +3,7 @@ - LiveGalGame - 对话编辑器 + LiveGalGame - 历史对话 diff --git a/desktop/src/renderer/index.html b/desktop/src/renderer/index.html index 7b33693..84da608 100644 --- a/desktop/src/renderer/index.html +++ b/desktop/src/renderer/index.html @@ -81,19 +81,19 @@

LiveGalGame

dashboard

总览

- + groups

攻略对象

- forum -

对话编辑器

+ history +

历史对话

developer_board

LLM 配置

- + settings

设置

diff --git a/desktop/src/renderer/settings.html b/desktop/src/renderer/settings.html new file mode 100644 index 0000000..b19eb91 --- /dev/null +++ b/desktop/src/renderer/settings.html @@ -0,0 +1,546 @@ + + + + + + 设置 - LiveGalGame + + + + +
+ + ← 返回 + + +

设置

+ + +
+
+ 🎤 音频权限 +
+ +
+
+ +
+
检测中...
+
正在检查麦克风权限
+
+
+ +
+ +
+
+
+ 🎵 音频测试 +
+
+ + +
+
+ + + +
+ 音量:0% +
+ +
+
+
+ + +
+
+ 🔑 API配置 +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + + + From f385e079cfe12b7bfd0751e14eef2bfff30f02bb Mon Sep 17 00:00:00 2001 From: chenspeculation Date: Fri, 14 Nov 2025 00:01:51 +0800 Subject: [PATCH 028/147] feat: integrate better-sqlite3 for database management and enhance character functionality - Added better-sqlite3 as a dependency for improved database handling. - Implemented DatabaseManager class for managing character data and conversations. - Created database schema and seed data for initial character setup. - Updated IPC handlers to support database operations for characters and conversations. - Enhanced frontend to load character and conversation data dynamically from the database. - Updated .gitignore to exclude new database files and build outputs. --- .gitignore | 8 +- desktop/package.json | 1 + desktop/pnpm-lock.yaml | 129 +++++ desktop/pnpm-workspace.yaml | 1 + desktop/src/db/database.js | 512 ++++++++++++++++++ desktop/src/db/schema.sql | 91 ++++ desktop/src/db/seed.sql | 89 +++ desktop/src/main.js | 102 ++++ desktop/src/preload.js | 12 +- desktop/src/renderer/characters.html | 278 +++++----- desktop/src/renderer/conversation-editor.html | 259 ++++++++- desktop/src/renderer/hud.html | 82 ++- desktop/src/renderer/index.html | 229 +++++--- desktop/src/renderer/js/renderer.js | 56 +- desktop/test-db.js | 69 +++ 15 files changed, 1589 insertions(+), 329 deletions(-) create mode 100644 desktop/src/db/database.js create mode 100644 desktop/src/db/schema.sql create mode 100644 desktop/src/db/seed.sql create mode 100644 desktop/test-db.js diff --git a/.gitignore b/.gitignore index 2627a50..0af2b0c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,10 @@ local.properties signing.properties /release.keystore /release -.vscode \ No newline at end of file +.vscode + +# Build output +/desktop/dist + +# Database files +/desktop/data \ No newline at end of file diff --git a/desktop/package.json b/desktop/package.json index 90beebb..dbe8885 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -22,6 +22,7 @@ "electron-builder": "^25.1.8" }, "dependencies": { + "better-sqlite3": "^12.4.1", "electron-store": "^8.2.0" }, "build": { diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index 87707db..4891c65 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + better-sqlite3: + specifier: ^12.4.1 + version: 12.4.1 electron-store: specifier: ^8.2.0 version: 8.2.0 @@ -262,6 +265,13 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + better-sqlite3@12.4.1: + resolution: {integrity: sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -317,6 +327,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -434,6 +447,10 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -564,6 +581,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} @@ -588,6 +609,9 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} @@ -653,6 +677,9 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -767,6 +794,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} @@ -992,6 +1022,9 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -1000,6 +1033,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + negotiator@0.6.4: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} @@ -1113,6 +1149,11 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -1143,6 +1184,10 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + read-binary-file-arch@1.0.6: resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} hasBin: true @@ -1241,6 +1286,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -1301,6 +1352,10 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -1309,6 +1364,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -1330,6 +1388,9 @@ packages: truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} @@ -1770,6 +1831,15 @@ snapshots: base64-js@1.5.1: {} + better-sqlite3@12.4.1: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -1876,6 +1946,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chownr@1.1.4: {} + chownr@2.0.0: {} chromium-pickle-js@0.2.0: {} @@ -1988,6 +2060,8 @@ snapshots: dependencies: mimic-response: 3.1.0 + deep-extend@0.6.0: {} + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -2164,6 +2238,8 @@ snapshots: escape-string-regexp@4.0.0: optional: true + expand-template@2.0.3: {} + exponential-backoff@3.1.3: {} extract-zip@2.0.1: @@ -2189,6 +2265,8 @@ snapshots: dependencies: pend: 1.2.0 + file-uri-to-path@1.0.0: {} + filelist@1.0.4: dependencies: minimatch: 5.1.6 @@ -2280,6 +2358,8 @@ snapshots: dependencies: pump: 3.0.3 + github-from-package@0.0.0: {} + glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -2428,6 +2508,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + ip-address@10.1.0: {} is-ci@3.0.1: @@ -2633,10 +2715,14 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + mkdirp-classic@0.5.3: {} + mkdirp@1.0.4: {} ms@2.1.3: {} + napi-build-utils@2.0.0: {} + negotiator@0.6.4: {} node-abi@3.80.0: @@ -2754,6 +2840,21 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.80.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + process-nextick-args@2.0.1: {} progress@2.0.3: {} @@ -2774,6 +2875,13 @@ snapshots: quick-lru@5.1.1: {} + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + read-binary-file-arch@1.0.6: dependencies: debug: 4.4.3 @@ -2871,6 +2979,14 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-update-notifier@2.0.0: dependencies: semver: 7.7.3 @@ -2941,6 +3057,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-json-comments@2.0.1: {} + sumchecker@3.0.1: dependencies: debug: 4.4.3 @@ -2951,6 +3069,13 @@ snapshots: dependencies: has-flag: 4.0.0 + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -2983,6 +3108,10 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-fest@0.13.1: optional: true diff --git a/desktop/pnpm-workspace.yaml b/desktop/pnpm-workspace.yaml index 5df7f4a..1aad50f 100644 --- a/desktop/pnpm-workspace.yaml +++ b/desktop/pnpm-workspace.yaml @@ -1,2 +1,3 @@ ignoredBuiltDependencies: + - better-sqlite3 - electron diff --git a/desktop/src/db/database.js b/desktop/src/db/database.js new file mode 100644 index 0000000..8cbd833 --- /dev/null +++ b/desktop/src/db/database.js @@ -0,0 +1,512 @@ +const Database = require('better-sqlite3'); +const path = require('path'); +const fs = require('fs'); + +class DatabaseManager { + constructor() { + // 数据库文件路径 + const dbPath = path.join(__dirname, '../../data/livegalgame.db'); + + // 确保data目录存在 + const dataDir = path.dirname(dbPath); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + + // 创建数据库连接 + this.db = new Database(dbPath, { + verbose: process.env.NODE_ENV === 'development' ? console.log : null + }); + + // 启用外键约束 + this.db.pragma('foreign_keys = ON'); + + // 初始化数据库表 + this.initialize(); + + console.log('Database initialized at:', dbPath); + } + + // 初始化数据库表 + initialize() { + console.log('Initializing database schema...'); + const schemaPath = path.join(__dirname, 'schema.sql'); + const schema = fs.readFileSync(schemaPath, 'utf-8'); + + // 执行SQL语句(分割并逐条执行) + const statements = schema.split(';').filter(stmt => stmt.trim()); + + // 开始事务 + const transaction = this.db.transaction(() => { + for (const statement of statements) { + if (statement.trim()) { + this.db.exec(statement); + } + } + }); + + transaction(); + console.log('Database schema initialized'); + + // 初始化示例数据(如果数据库为空) + this.seedSampleData(); + } + + // 关闭数据库连接 + close() { + if (this.db) { + this.db.close(); + } + } + + // ========== 角色相关方法 ========== + + // 创建角色 + createCharacter(characterData) { + const stmt = this.db.prepare(` + INSERT INTO characters (id, name, nickname, relationship_label, avatar_color, affinity, created_at, updated_at, notes) + VALUES (@id, @name, @nickname, @relationship_label, @avatar_color, @affinity, @created_at, @updated_at, @notes) + `); + + const info = stmt.run({ + id: characterData.id || this.generateId(), + name: characterData.name, + nickname: characterData.nickname || null, + relationship_label: characterData.relationship_label || null, + avatar_color: characterData.avatar_color || '#ff6b6b', + affinity: characterData.affinity || 50, + created_at: Date.now(), + updated_at: Date.now(), + notes: characterData.notes || null + }); + + return this.getCharacterById(characterData.id || info.lastInsertRowid); + } + + // 获取所有角色 + getAllCharacters() { + const stmt = this.db.prepare(` + SELECT c.*, + GROUP_CONCAT(t.name) as tags + FROM characters c + LEFT JOIN character_tags ct ON c.id = ct.character_id + LEFT JOIN tags t ON ct.tag_id = t.id + GROUP BY c.id + ORDER BY c.updated_at DESC + `); + + return stmt.all().map(row => ({ + ...row, + tags: row.tags ? row.tags.split(',') : [] + })); + } + + // 获取单个角色 + getCharacterById(id) { + const stmt = this.db.prepare(` + SELECT c.*, + GROUP_CONCAT(t.name) as tags + FROM characters c + LEFT JOIN character_tags ct ON c.id = ct.character_id + LEFT JOIN tags t ON ct.tag_id = t.id + WHERE c.id = ? + GROUP BY c.id + `); + + const row = stmt.get(id); + if (!row) return null; + + return { + ...row, + tags: row.tags ? row.tags.split(',') : [] + }; + } + + // 更新角色 + updateCharacter(id, updates) { + const fields = []; + const values = { id }; + + for (const [key, value] of Object.entries(updates)) { + if (key !== 'id' && key !== 'tags') { + fields.push(`${key} = @${key}`); + values[key] = value; + } + } + + fields.push('updated_at = @updated_at'); + values.updated_at = Date.now(); + + const stmt = this.db.prepare(` + UPDATE characters + SET ${fields.join(', ')} + WHERE id = @id + `); + + stmt.run(values); + return this.getCharacterById(id); + } + + // ========== 对话相关方法 ========== + + // 创建对话 + createConversation(conversationData) { + const stmt = this.db.prepare(` + INSERT INTO conversations (id, character_id, title, date, affinity_change, summary, tags, created_at, updated_at) + VALUES (@id, @character_id, @title, @date, @affinity_change, @summary, @tags, @created_at, @updated_at) + `); + + const info = stmt.run({ + id: conversationData.id || this.generateId(), + character_id: conversationData.character_id, + title: conversationData.title || null, + date: conversationData.date || Date.now(), + affinity_change: conversationData.affinity_change || 0, + summary: conversationData.summary || null, + tags: conversationData.tags || null, + created_at: Date.now(), + updated_at: Date.now() + }); + + return this.getConversationById(conversationData.id || info.lastInsertRowid); + } + + // 获取角色的所有对话(带角色信息和消息数) + getConversationsByCharacter(characterId) { + const stmt = this.db.prepare(` + SELECT + c.*, + char.name as character_name, + char.avatar_color as character_avatar_color, + char.id as character_id, + COUNT(m.id) as message_count + FROM conversations c + INNER JOIN characters char ON c.character_id = char.id + LEFT JOIN messages m ON c.id = m.conversation_id + WHERE c.character_id = ? + GROUP BY c.id + ORDER BY c.updated_at DESC + `); + + return stmt.all(characterId); + } + + // 获取单个对话 + getConversationById(id) { + const stmt = this.db.prepare('SELECT * FROM conversations WHERE id = ?'); + return stmt.get(id); + } + + // ========== 消息相关方法 ========== + + // 创建消息 + createMessage(messageData) { + const stmt = this.db.prepare(` + INSERT INTO messages (id, conversation_id, sender, content, timestamp, is_ai_generated) + VALUES (@id, @conversation_id, @sender, @content, @timestamp, @is_ai_generated) + `); + + const info = stmt.run({ + id: messageData.id || this.generateId(), + conversation_id: messageData.conversation_id, + sender: messageData.sender, // 'user' or 'character' + content: messageData.content, + timestamp: messageData.timestamp || Date.now(), + is_ai_generated: messageData.is_ai_generated ? 1 : 0 + }); + + return this.getMessageById(messageData.id || info.lastInsertRowid); + } + + // 获取对话的所有消息 + getMessagesByConversation(conversationId) { + const stmt = this.db.prepare(` + SELECT * FROM messages + WHERE conversation_id = ? + ORDER BY timestamp ASC + `); + + return stmt.all(conversationId); + } + + // 获取单个消息 + getMessageById(id) { + const stmt = this.db.prepare('SELECT * FROM messages WHERE id = ?'); + return stmt.get(id); + } + + // ========== 标签相关方法 ========== + + // 创建标签 + createTag(tagData) { + const stmt = this.db.prepare(` + INSERT OR IGNORE INTO tags (id, name, color) + VALUES (@id, @name, @color) + `); + + const info = stmt.run({ + id: tagData.id || this.generateId(), + name: tagData.name, + color: tagData.color || 'primary' + }); + + return this.getTagById(tagData.id || info.lastInsertRowid); + } + + // 获取所有标签 + getAllTags() { + const stmt = this.db.prepare('SELECT * FROM tags ORDER BY name'); + return stmt.all(); + } + + // 获取单个标签 + getTagById(id) { + const stmt = this.db.prepare('SELECT * FROM tags WHERE id = ?'); + return stmt.get(id); + } + + // 为角色添加标签 + addTagToCharacter(characterId, tagId) { + const stmt = this.db.prepare(` + INSERT OR IGNORE INTO character_tags (character_id, tag_id) + VALUES (?, ?) + `); + + stmt.run(characterId, tagId); + } + + // ========== 统计相关方法 ========== + + // 获取对话总数 + getConversationCount() { + const stmt = this.db.prepare('SELECT COUNT(*) as count FROM conversations'); + return stmt.get().count; + } + + // 获取消息总数 + getMessageCount() { + const stmt = this.db.prepare('SELECT COUNT(*) as count FROM messages'); + return stmt.get().count; + } + + // 获取角色的对话统计 + getCharacterStats(characterId) { + const stmt = this.db.prepare(` + SELECT + COUNT(DISTINCT c.id) as conversation_count, + COUNT(m.id) as message_count, + MAX(c.date) as last_conversation_date + FROM conversations c + LEFT JOIN messages m ON c.id = m.conversation_id + WHERE c.character_id = ? + `); + + return stmt.get(characterId); + } + + // 获取统计数据 + getStatistics() { + const characterCount = this.db.prepare('SELECT COUNT(*) as count FROM characters').get().count; + const conversationCount = this.db.prepare('SELECT COUNT(*) as count FROM conversations').get().count; + const messageCount = this.db.prepare('SELECT COUNT(*) as count FROM messages').get().count; + + // 计算平均好感度 + const avgAffinity = this.db.prepare('SELECT AVG(affinity) as avg FROM characters').get().avg || 0; + + return { + characterCount, + conversationCount, + messageCount, + avgAffinity: Math.round(avgAffinity) + }; + } + + // 获取最近对话(带角色信息) + getRecentConversations(limit = 10) { + const stmt = this.db.prepare(` + SELECT + c.*, + char.name as character_name, + char.avatar_color as character_avatar_color, + char.id as character_id, + COUNT(m.id) as message_count + FROM conversations c + INNER JOIN characters char ON c.character_id = char.id + LEFT JOIN messages m ON c.id = m.conversation_id + GROUP BY c.id + ORDER BY c.updated_at DESC + LIMIT ? + `); + + return stmt.all(limit); + } + + // 获取所有对话(带角色信息) + getAllConversations() { + const stmt = this.db.prepare(` + SELECT + c.*, + char.name as character_name, + char.avatar_color as character_avatar_color, + char.id as character_id, + COUNT(m.id) as message_count + FROM conversations c + INNER JOIN characters char ON c.character_id = char.id + LEFT JOIN messages m ON c.id = m.conversation_id + GROUP BY c.id + ORDER BY c.updated_at DESC + `); + + return stmt.all(); + } + + // 获取对话的消息 + getMessagesByConversation(conversationId) { + const stmt = this.db.prepare(` + SELECT * FROM messages + WHERE conversation_id = ? + ORDER BY timestamp ASC + `); + + return stmt.all(conversationId); + } + + // ========== 工具方法 ========== + + // 生成ID + generateId() { + return Date.now().toString(36) + Math.random().toString(36).substr(2); + } + + // 批量插入示例数据(从SQL文件加载) + seedSampleData() { + console.log('Seeding sample data...'); + + // 检查对话数据是否存在 + const conversationCount = this.db.prepare('SELECT COUNT(*) as count FROM conversations').get().count; + const characterCount = this.db.prepare('SELECT COUNT(*) as count FROM characters').get().count; + + console.log(`Current database state: ${characterCount} characters, ${conversationCount} conversations`); + + // 如果对话数据已存在,跳过 + if (conversationCount > 0) { + console.log('Conversation data already exists, skipping seed...'); + return; + } + + // 如果没有角色数据,需要先插入角色 + if (characterCount === 0) { + console.log('No characters found, will insert all data including characters'); + } else { + console.log('Characters exist, will only insert conversations and messages'); + } + + // 如果角色数据不存在,需要先插入角色数据 + const needCharacters = characterCount === 0; + + try { + // 读取并执行SQL种子文件 + const seedPath = path.join(__dirname, 'seed.sql'); + if (fs.existsSync(seedPath)) { + const seedSQL = fs.readFileSync(seedPath, 'utf-8'); + + // 改进SQL语句分割:先移除注释行,然后按分号分割 + const lines = seedSQL.split('\n'); + let cleanedLines = []; + let inMultiLineStatement = false; + let currentStatement = ''; + + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim(); + + // 跳过空行和纯注释行 + if (!line || line.startsWith('--')) { + continue; + } + + // 移除行内注释(-- 后面的内容) + const commentIndex = line.indexOf('--'); + if (commentIndex >= 0) { + line = line.substring(0, commentIndex).trim(); + if (!line) continue; + } + + // 累积到当前语句 + currentStatement += (currentStatement ? ' ' : '') + line; + + // 如果行以分号结尾,说明语句完整 + if (line.endsWith(';')) { + const statement = currentStatement.slice(0, -1).trim(); // 移除末尾的分号 + if (statement) { + cleanedLines.push(statement); + } + currentStatement = ''; + } + } + + // 处理最后可能没有分号的语句 + if (currentStatement.trim()) { + cleanedLines.push(currentStatement.trim()); + } + + console.log(`Found ${cleanedLines.length} SQL statements to execute`); + + const transaction = this.db.transaction(() => { + for (let i = 0; i < cleanedLines.length; i++) { + const statement = cleanedLines[i]; + + // 如果角色数据已存在,跳过角色相关的INSERT语句 + if (!needCharacters && statement.toUpperCase().includes('INSERT') && + (statement.includes('INSERT INTO characters') || + statement.includes('INSERT INTO tags') || + statement.includes('INSERT INTO character_tags'))) { + console.log(`Skipping statement ${i + 1}: character data (already exists)`); + continue; + } + + try { + // 执行SQL语句(添加分号) + this.db.exec(statement + ';'); + if (statement.includes('INSERT INTO conversations')) { + console.log(`✓ Executed conversation INSERT statement ${i + 1}`); + } + } catch (err) { + // 忽略重复插入的错误(INSERT OR IGNORE 会处理) + if (err.message.includes('UNIQUE constraint') || err.message.includes('already exists')) { + console.log(`Statement ${i + 1}: skipped (duplicate)`); + } else { + console.error(`Error executing statement ${i + 1}:`, err.message); + console.error('Statement preview:', statement.substring(0, 150) + '...'); + // 继续执行其他语句,不中断 + } + } + } + }); + + transaction(); + console.log('Sample data seeded successfully from SQL file'); + + // 验证数据插入 + const finalConvCount = this.db.prepare('SELECT COUNT(*) as count FROM conversations').get().count; + const finalMsgCount = this.db.prepare('SELECT COUNT(*) as count FROM messages').get().count; + const finalCharCount = this.db.prepare('SELECT COUNT(*) as count FROM characters').get().count; + console.log(`Data verification: ${finalCharCount} characters, ${finalConvCount} conversations, ${finalMsgCount} messages`); + + if (finalConvCount === 0) { + console.warn('⚠️ Warning: No conversations were inserted!'); + console.warn('This might indicate a SQL parsing or execution issue.'); + } else { + console.log('✅ Data seeding completed successfully'); + } + } else { + console.warn('Seed SQL file not found, skipping data seeding'); + } + } catch (error) { + console.error('Error seeding sample data:', error); + console.error(error.stack); + // 不抛出错误,允许应用继续运行 + } + } +} + +module.exports = DatabaseManager; diff --git a/desktop/src/db/schema.sql b/desktop/src/db/schema.sql new file mode 100644 index 0000000..d3b3d0f --- /dev/null +++ b/desktop/src/db/schema.sql @@ -0,0 +1,91 @@ +-- LiveGalGame Database Schema + +-- 攻略对象表 +CREATE TABLE IF NOT EXISTS characters ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + nickname TEXT, + relationship_label TEXT, -- 关系标签(青梅竹马、学生会长等) + avatar_color TEXT, -- 头像颜色 + affinity INTEGER DEFAULT 50, -- 好感度(0-100) + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + notes TEXT -- 备注 +); + +-- 关键词标签表(用于角色特点) +CREATE TABLE IF NOT EXISTS tags ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + color TEXT DEFAULT 'primary' -- 标签颜色 +); + +-- 角色-标签关联表 +CREATE TABLE IF NOT EXISTS character_tags ( + character_id TEXT NOT NULL, + tag_id TEXT NOT NULL, + PRIMARY KEY (character_id, tag_id), + FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE +); + +-- 对话记录表 +CREATE TABLE IF NOT EXISTS conversations ( + id TEXT PRIMARY KEY, + character_id TEXT NOT NULL, + title TEXT, -- 对话标题 + date INTEGER NOT NULL, -- 对话日期(时间戳) + affinity_change INTEGER DEFAULT 0, -- 好感度变化 + summary TEXT, -- 对话摘要 + tags TEXT, -- 标签(逗号分隔) + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE +); + +-- 消息记录表 +CREATE TABLE IF NOT EXISTS messages ( + id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL, + sender TEXT NOT NULL, -- 'user' or 'character' + content TEXT NOT NULL, + timestamp INTEGER NOT NULL, + is_ai_generated INTEGER DEFAULT 0, -- 是否AI生成 + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE +); + +-- AI分析记录表 +CREATE TABLE IF NOT EXISTS ai_analysis ( + id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL, + message_id TEXT, -- 关联的具体消息(可选) + insight_type TEXT, -- 洞察类型(情感分析、建议等) + content TEXT NOT NULL, -- 分析内容 + created_at INTEGER NOT NULL, + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, + FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE +); + +-- AI建议记录表 +CREATE TABLE IF NOT EXISTS ai_suggestions ( + id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL, + message_id TEXT, -- 关联的具体消息(可选) + title TEXT NOT NULL, + content TEXT NOT NULL, + affinity_prediction INTEGER, -- 好感度变化预测 + tags TEXT, -- 标签(逗号分隔) + is_used INTEGER DEFAULT 0, -- 是否被采用 + created_at INTEGER NOT NULL, + FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE, + FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE +); + +-- 索引优化 +CREATE INDEX IF NOT EXISTS idx_conversations_character_id ON conversations(character_id); +CREATE INDEX IF NOT EXISTS idx_conversations_date ON conversations(date); +CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id); +CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp); +CREATE INDEX IF NOT EXISTS idx_character_tags_character_id ON character_tags(character_id); +CREATE INDEX IF NOT EXISTS idx_ai_analysis_conversation_id ON ai_analysis(conversation_id); +CREATE INDEX IF NOT EXISTS idx_ai_suggestions_conversation_id ON ai_suggestions(conversation_id); diff --git a/desktop/src/db/seed.sql b/desktop/src/db/seed.sql new file mode 100644 index 0000000..6e5cdaa --- /dev/null +++ b/desktop/src/db/seed.sql @@ -0,0 +1,89 @@ +-- LiveGalGame 示例数据初始化脚本 + +-- 插入角色数据 +INSERT OR IGNORE INTO characters (id, name, nickname, relationship_label, avatar_color, affinity, created_at, updated_at, notes) VALUES +('miyu', 'Miyu', '小咪', '青梅竹马', '#ff6b6b', 75, 1735689600000, 1735689600000, '活泼可爱的青梅竹马,喜欢樱花'), +('akira', 'Akira', '会长', '学生会长', '#4ecdc4', 60, 1735689600000, 1735689600000, '认真负责的学生会长,做事有条理'), +('hana', 'Hana', '花酱', '图书馆管理员', '#ffe66d', 45, 1735689600000, 1735689600000, '文静内向的图书管理员,喜欢读书'); + +-- 插入标签数据 +INSERT OR IGNORE INTO tags (id, name, color) VALUES +('tag_active', '主动', 'primary'), +('tag_caring', '体贴', 'success'), +('tag_emotional', '情感', 'primary'), +('tag_sincere', '真诚', 'warning'), +('tag_gentle', '温柔', 'primary'), +('tag_serious', '认真', 'primary'); + +-- 关联角色和标签 +INSERT OR IGNORE INTO character_tags (character_id, tag_id) VALUES +('miyu', 'tag_active'), +('miyu', 'tag_caring'), +('akira', 'tag_serious'), +('akira', 'tag_sincere'), +('hana', 'tag_gentle'), +('hana', 'tag_emotional'); + +-- 插入对话数据 +INSERT OR IGNORE INTO conversations (id, character_id, title, date, affinity_change, summary, tags, created_at, updated_at) VALUES +-- Miyu 的对话 +('conv_miyu_1', 'miyu', '樱花下的约定', 1735689600000, 10, '一起讨论了去公园看樱花的计划', '愉快,日常', 1735689600000, 1735689600000), +('conv_miyu_2', 'miyu', '午后的咖啡时光', 1735689700000, 8, '在咖啡厅偶遇,一起聊天', '日常,轻松', 1735689700000, 1735689700000), +('conv_miyu_3', 'miyu', '回忆童年时光', 1735689800000, 12, '聊起了小时候一起玩耍的回忆', '回忆,温馨', 1735689800000, 1735689800000), +-- Akira 的对话 +('conv_akira_1', 'akira', '学生会的会议', 1735689601000, 5, '讨论了学生会的工作安排', '工作,认真', 1735689601000, 1735689601000), +('conv_akira_2', 'akira', '文化节筹备', 1735689701000, 7, '一起策划文化节的活动', '工作,合作', 1735689701000, 1735689701000), +('conv_akira_3', 'akira', '学习小组', 1735689801000, 6, '讨论学习方法和考试准备', '学习,互助', 1735689801000, 1735689801000), +-- Hana 的对话 +('conv_hana_1', 'hana', '图书馆的偶遇', 1735689602000, 8, '在图书馆偶遇并聊起了喜欢的书籍', '日常,读书', 1735689602000, 1735689602000), +('conv_hana_2', 'hana', '推荐好书', 1735689702000, 9, '互相推荐喜欢的书籍和作者', '读书,分享', 1735689702000, 1735689702000), +('conv_hana_3', 'hana', '安静的阅读时光', 1735689802000, 7, '一起在图书馆安静地阅读', '读书,安静', 1735689802000, 1735689802000); + +-- 插入消息数据 +INSERT OR IGNORE INTO messages (id, conversation_id, sender, content, timestamp, is_ai_generated) VALUES +-- Miyu 对话1的消息 +('msg_miyu_1_1', 'conv_miyu_1', 'character', '哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。', 1735689600000, 0), +('msg_miyu_1_2', 'conv_miyu_1', 'user', '听起来真不错!也许我们可以一起去?', 1735689601000, 0), +('msg_miyu_1_3', 'conv_miyu_1', 'character', '太好了!我一直想和你一起去散步呢。', 1735689602000, 0), +('msg_miyu_1_4', 'conv_miyu_1', 'user', '我知道附近有个很棒的公园,樱花特别美,要不要去那里?', 1735689603000, 0), +-- Miyu 对话2的消息 +('msg_miyu_2_1', 'conv_miyu_2', 'character', '好巧啊,你也在这里!', 1735689700000, 0), +('msg_miyu_2_2', 'conv_miyu_2', 'user', '是啊,来喝杯咖啡放松一下。', 1735689701000, 0), +('msg_miyu_2_3', 'conv_miyu_2', 'character', '那我们一起坐吧,我正好也想找人聊聊天。', 1735689702000, 0), +('msg_miyu_2_4', 'conv_miyu_2', 'user', '当然可以!', 1735689703000, 0), +-- Miyu 对话3的消息 +('msg_miyu_3_1', 'conv_miyu_3', 'character', '你还记得我们小时候一起爬树的事情吗?', 1735689800000, 0), +('msg_miyu_3_2', 'conv_miyu_3', 'user', '当然记得!那时候你总是爬得比我高。', 1735689801000, 0), +('msg_miyu_3_3', 'conv_miyu_3', 'character', '哈哈,现在想想真是美好的回忆呢。', 1735689802000, 0), +-- Akira 对话1的消息 +('msg_akira_1_1', 'conv_akira_1', 'character', '关于下周的学生会会议,你有什么想法吗?', 1735689601000, 0), +('msg_akira_1_2', 'conv_akira_1', 'user', '我觉得可以讨论一下文化节的活动安排。', 1735689602000, 0), +('msg_akira_1_3', 'conv_akira_1', 'character', '好主意!那我们明天详细讨论一下。', 1735689603000, 0), +-- Akira 对话2的消息 +('msg_akira_2_1', 'conv_akira_2', 'character', '文化节的活动方案我已经看过了,有几个地方需要调整。', 1735689701000, 0), +('msg_akira_2_2', 'conv_akira_2', 'user', '好的,我们一起看看哪些地方可以改进。', 1735689702000, 0), +('msg_akira_2_3', 'conv_akira_2', 'character', '谢谢你的配合,有你在真是太好了。', 1735689703000, 0), +-- Akira 对话3的消息 +('msg_akira_3_1', 'conv_akira_3', 'character', '这次考试你准备得怎么样?', 1735689801000, 0), +('msg_akira_3_2', 'conv_akira_3', 'user', '还在复习中,有些地方不太明白。', 1735689802000, 0), +('msg_akira_3_3', 'conv_akira_3', 'character', '那我们可以一起学习,互相帮助。', 1735689803000, 0), +-- Hana 对话1的消息 +('msg_hana_1_1', 'conv_hana_1', 'character', '你也喜欢这本书吗?', 1735689602000, 0), +('msg_hana_1_2', 'conv_hana_1', 'user', '是的,我很喜欢作者的写作风格。', 1735689603000, 0), +('msg_hana_1_3', 'conv_hana_1', 'character', '我也是!这本书我已经读了好几遍了。', 1735689604000, 0), +-- Hana 对话2的消息 +('msg_hana_2_1', 'conv_hana_2', 'character', '我最近发现了一本很棒的小说,推荐给你。', 1735689702000, 0), +('msg_hana_2_2', 'conv_hana_2', 'user', '太好了!我也正好想找新书看。', 1735689703000, 0), +('msg_hana_2_3', 'conv_hana_2', 'character', '那下次我们可以一起讨论读后感。', 1735689704000, 0), +-- Hana 对话3的消息 +('msg_hana_3_1', 'conv_hana_3', 'character', '图书馆真是个安静的好地方。', 1735689802000, 0), +('msg_hana_3_2', 'conv_hana_3', 'user', '是啊,在这里读书心情都会变得平静。', 1735689803000, 0), +('msg_hana_3_3', 'conv_hana_3', 'character', '那我们以后可以经常一起来。', 1735689804000, 0); + +-- 插入AI建议数据 +INSERT OR IGNORE INTO ai_suggestions (id, conversation_id, message_id, title, content, affinity_prediction, tags, is_used, created_at) VALUES +('sugg_miyu_1', 'conv_miyu_1', 'msg_miyu_1_2', '提议具体地点', '我知道附近有个很棒的公园,樱花特别美,要不要去那里?', 15, '主动,体贴', 1, 1735689603000), +('sugg_miyu_2', 'conv_miyu_1', 'msg_miyu_1_2', '表达期待', '太好了!我一直想和你一起去散步呢。', 10, '情感,真诚', 0, 1735689602000), +('sugg_akira_1', 'conv_akira_2', 'msg_akira_2_2', '主动配合', '好的,我们一起看看哪些地方可以改进。', 8, '合作,积极', 1, 1735689702000), +('sugg_hana_1', 'conv_hana_2', 'msg_hana_2_2', '表达兴趣', '太好了!我也正好想找新书看。', 12, '兴趣,共鸣', 1, 1735689703000); + diff --git a/desktop/src/main.js b/desktop/src/main.js index 6fef3f2..cc4fdc7 100644 --- a/desktop/src/main.js +++ b/desktop/src/main.js @@ -1,9 +1,11 @@ const { app, BrowserWindow, globalShortcut, ipcMain } = require('electron'); const path = require('path'); +const DatabaseManager = require('./db/database'); // 主窗口实例 let mainWindow; let hudWindow; +let db; function createWindow() { // 创建浏览器窗口 @@ -123,6 +125,99 @@ function setupIPC() { }); console.log('IPC通信已设置'); + + // 数据库IPC处理器 + if (!db) { + db = new DatabaseManager(); + // 不再自动初始化示例数据,数据需要手动在数据库中准备 + } + + // 获取所有角色 + ipcMain.handle('db-get-all-characters', () => { + try { + return db.getAllCharacters(); + } catch (error) { + console.error('Error getting all characters:', error); + return []; + } + }); + + // 获取单个角色 + ipcMain.handle('db-get-character-by-id', (event, id) => { + try { + return db.getCharacterById(id); + } catch (error) { + console.error('Error getting character:', error); + return null; + } + }); + + // 创建角色 + ipcMain.handle('db-create-character', (event, characterData) => { + try { + return db.createCharacter(characterData); + } catch (error) { + console.error('Error creating character:', error); + return null; + } + }); + + // 获取角色的对话 + ipcMain.handle('db-get-conversations-by-character', (event, characterId) => { + try { + return db.getConversationsByCharacter(characterId); + } catch (error) { + console.error('Error getting conversations:', error); + return []; + } + }); + + // 获取对话的消息 + ipcMain.handle('db-get-messages-by-conversation', (event, conversationId) => { + try { + return db.getMessagesByConversation(conversationId); + } catch (error) { + console.error('Error getting messages:', error); + return []; + } + }); + + // 获取统计数据 + ipcMain.handle('db-get-statistics', () => { + try { + return db.getStatistics(); + } catch (error) { + console.error('Error getting statistics:', error); + return { + characterCount: 0, + conversationCount: 0, + messageCount: 0, + avgAffinity: 0 + }; + } + }); + + // 获取最近对话 + ipcMain.handle('db-get-recent-conversations', (event, limit) => { + try { + return db.getRecentConversations(limit || 10); + } catch (error) { + console.error('Error getting recent conversations:', error); + return []; + } + }); + + // 获取所有对话 + ipcMain.handle('db-get-all-conversations', () => { + try { + return db.getAllConversations(); + } catch (error) { + console.error('Error getting all conversations:', error); + return []; + } + }); + + console.log('Database IPC handlers registered'); } // 创建HUD窗口 @@ -158,6 +253,13 @@ function createHUDWindow() { title: 'LiveGalGame HUD' }); + // 初始化数据库 + if (!db) { + db = new DatabaseManager(); + // 不再自动初始化示例数据 + } + + // 确保窗口可以调整大小(显式设置) hudWindow.setResizable(true); diff --git a/desktop/src/preload.js b/desktop/src/preload.js index 4133428..822fe9a 100644 --- a/desktop/src/preload.js +++ b/desktop/src/preload.js @@ -22,7 +22,17 @@ contextBridge.exposeInMainWorld('electronAPI', { // HUD拖拽 startHUDDrag: (pos) => ipcRenderer.send('start-hud-drag', pos), updateHUDDrag: (pos) => ipcRenderer.send('update-hud-drag', pos), - endHUDDrag: () => ipcRenderer.send('end-hud-drag') + endHUDDrag: () => ipcRenderer.send('end-hud-drag'), + + // 数据库API + getAllCharacters: () => ipcRenderer.invoke('db-get-all-characters'), + getCharacterById: (id) => ipcRenderer.invoke('db-get-character-by-id', id), + createCharacter: (characterData) => ipcRenderer.invoke('db-create-character', characterData), + getConversationsByCharacter: (characterId) => ipcRenderer.invoke('db-get-conversations-by-character', characterId), + getMessagesByConversation: (conversationId) => ipcRenderer.invoke('db-get-messages-by-conversation', conversationId), + getStatistics: () => ipcRenderer.invoke('db-get-statistics'), + getRecentConversations: (limit) => ipcRenderer.invoke('db-get-recent-conversations', limit), + getAllConversations: () => ipcRenderer.invoke('db-get-all-conversations') }); // 监听主进程消息 diff --git a/desktop/src/renderer/characters.html b/desktop/src/renderer/characters.html index a56bb51..e1606b5 100644 --- a/desktop/src/renderer/characters.html +++ b/desktop/src/renderer/characters.html @@ -114,155 +114,11 @@

- -
-
-
M
-
-

Miyu

-

青梅竹马

-
-
- -
-
- 好感度 - 75% -
-
-
-
-
- -
-
- 最近对话 - 2小时前 -
-
- 对话次数 - 12 -
-
- -
-

关键词

-
- 活泼 - 可爱 - 关心 -
-
- -
- - -
-
- - -
-
-
A
-
-

Akira

-

学生会长

-
-
- -
-
- 好感度 - 60% -
-
-
-
-
- -
-
- 最近对话 - 1天前 -
-
- 对话次数 - 8 -
-
- -
-

关键词

-
- 认真 - 负责 - 温柔 -
-
- -
- - -
-
- - -
-
-
H
-
-

Hana

-

图书馆管理员

-
-
- -
-
- 好感度 - 45% -
-
-
-
-
- -
-
- 最近对话 - 3天前 -
-
- 对话次数 - 5 -
-
- -
-

关键词

-
- 文静 - 书虫 - 害羞 -
-
- -
- - -
+
+ +
+
+

加载中...

@@ -284,7 +140,129 @@

Hana

alert(`查看 ${characterId} 的详细信息\n\n这里可以显示:\n- 角色档案\n- 性格特点\n- 喜好厌恶\n- 重要事件\n- 对话总结`); } - console.log('Characters page loaded'); + // 加载角色数据 + async function loadCharacters() { + try { + // 从数据库加载数据 + if (window.electronAPI && window.electronAPI.getAllCharacters) { + const characters = await window.electronAPI.getAllCharacters(); + + if (characters.length === 0) { + // 数据库为空,显示空状态 + const container = document.getElementById('characters-container'); + const loadingIndicator = document.getElementById('loading-indicator'); + loadingIndicator.style.display = 'none'; + container.innerHTML = ` +
+

还没有角色数据

+

请在数据库中添加角色数据

+
+ `; + } else { + renderCharacters(characters); + } + } else { + console.error('Database API not available'); + document.getElementById('loading-indicator').innerHTML = '

数据库API不可用

'; + } + } catch (error) { + console.error('Failed to load characters:', error); + document.getElementById('loading-indicator').innerHTML = '

加载失败:' + error.message + '

'; + } + } + + // 渲染角色卡片 + function renderCharacters(characters) { + const container = document.getElementById('characters-container'); + const loadingIndicator = document.getElementById('loading-indicator'); + + // 隐藏加载指示器 + loadingIndicator.style.display = 'none'; + + // 清空容器 + container.innerHTML = ''; + + // 渲染每个角色 + characters.forEach(character => { + const characterCard = createCharacterCard(character); + container.appendChild(characterCard); + }); + } + + // 创建角色卡片 + function createCharacterCard(character) { + const card = document.createElement('div'); + card.className = 'bg-white dark:bg-surface-dark rounded-xl p-6 border border-surface-light dark:border-surface-dark hover:shadow-lg transition-shadow'; + + const firstLetter = character.name.charAt(0); + const avatarGradient = getAvatarGradient(character.avatar_color); + + card.innerHTML = ` +
+
+ ${firstLetter} +
+
+

${character.name}

+

${character.relationship_label}

+
+
+ +
+
+ 好感度 + ${character.affinity}% +
+
+
+
+
+ +
+
+ 角色ID + ${character.id} +
+
+ 创建时间 + ${new Date(character.created_at).toLocaleDateString('zh-CN')} +
+
+ +
+

关键词

+
+ ${character.tags.map(tag => `${tag}`).join('')} +
+
+ +
+ + +
+ `; + + return card; + } + + // 获取头像渐变样式 + function getAvatarGradient(color) { + // 简单的渐变生成逻辑 + if (color.includes('ff6b6b')) return 'bg-gradient-to-br from-[#ff6b6b] to-[#ff8e8e]'; + if (color.includes('4ecdc4')) return 'bg-gradient-to-br from-[#4ecdc4] to-[#6ee5dd]'; + if (color.includes('ffe66d')) return 'bg-gradient-to-br from-[#ffe66d] to-[#fff099]'; + return 'bg-gradient-to-br from-primary to-[#8e24aa]'; + } + + // 页面加载时初始化 + document.addEventListener('DOMContentLoaded', () => { + console.log('Characters page loaded'); + loadCharacters(); + }); diff --git a/desktop/src/renderer/conversation-editor.html b/desktop/src/renderer/conversation-editor.html index 045bc75..23e9a26 100644 --- a/desktop/src/renderer/conversation-editor.html +++ b/desktop/src/renderer/conversation-editor.html @@ -75,32 +75,10 @@

-
-
-
-

Miyu

-

哦,真的吗?我刚才还在想...

-
-
-
-
-
-

Akira

-

别忘了我们的约定!

-
-
-
-
-
-

Hana

-

我今天发现了一家超可爱的小咖啡馆。

-
+ +
+
+

加载中...

@@ -130,8 +108,8 @@

-

欢迎使用对话编辑器

-

点击左侧对话列表开始编辑

+

历史对话

+

点击左侧对话列表查看详情

@@ -279,5 +257,230 @@

+ diff --git a/desktop/src/renderer/hud.html b/desktop/src/renderer/hud.html index e4596eb..567cbfc 100644 --- a/desktop/src/renderer/hud.html +++ b/desktop/src/renderer/hud.html @@ -222,13 +222,10 @@
- -
-
哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。
-
- -
-
听起来真不错!也许我们可以一起去?
+ +
+
+

加载中...

@@ -356,6 +353,77 @@ console.log('HUD script loaded successfully'); console.log('electronAPI available:', !!window.electronAPI); + + // 加载最近对话的消息 + async function loadRecentMessages() { + try { + if (window.electronAPI && window.electronAPI.getRecentConversations) { + // 获取最近的对话 + const conversations = await window.electronAPI.getRecentConversations(1); + + if (conversations.length === 0) { + document.getElementById('transcript-messages').innerHTML = ` +
+

还没有对话记录

+
+ `; + return; + } + + // 获取该对话的消息 + const conversationId = conversations[0].id; + const messages = await window.electronAPI.getMessagesByConversation(conversationId); + + renderMessages(messages); + } else { + console.error('Database API not available'); + document.getElementById('transcript-messages').innerHTML = ` +
+

数据库API不可用

+
+ `; + } + } catch (error) { + console.error('Failed to load messages:', error); + document.getElementById('transcript-messages').innerHTML = ` +
+

加载失败:${error.message}

+
+ `; + } + } + + // 渲染消息 + function renderMessages(messages) { + const container = document.getElementById('transcript-messages'); + + if (messages.length === 0) { + container.innerHTML = ` +
+

该对话还没有消息

+
+ `; + return; + } + + container.innerHTML = messages.map(msg => { + const isUser = msg.sender === 'user'; + return ` +
+
${msg.content}
+
+ `; + }).join(''); + + // 滚动到底部 + container.scrollTop = container.scrollHeight; + } + + // 页面加载时加载消息 + document.addEventListener('DOMContentLoaded', () => { + console.log('HUD page loaded'); + loadRecentMessages(); + }); diff --git a/desktop/src/renderer/index.html b/desktop/src/renderer/index.html index 84da608..a2ef7b5 100644 --- a/desktop/src/renderer/index.html +++ b/desktop/src/renderer/index.html @@ -123,42 +123,11 @@

-
-
- groups -
-
-

攻略对象

-

12

-
-
-
-
- chat_bubble -
-
-

对话

-

89

-
-
-
-
- account_tree -
-
-

分支

-

256

-
-
-
-
- flag -
-
-

故事标记

-

42

-
+
+ +
+
+

加载中...

@@ -181,74 +150,156 @@

最近对话<

-
-
-
-
-

第1章:命运的相遇

-
-
-
-
-
-

优衣在樱花节上初次遇见健司的开场场景。确立了他们最初的动态关系。

-
-
-

最后编辑:2天前

- -
+
+ +
+
+

加载中...

+
+
+ +
-
-
-
-

咖啡馆的误会

-
-
-
-
-
-

一个关键的冲突场景。健司看到优衣和她的童年朋友明说话,导致了一个主要的情节点。

-
-
-

最后编辑:5天前

- -
+ + + // 页面加载时初始化 + document.addEventListener('DOMContentLoaded', () => { + console.log('Index page loaded'); + loadStatistics(); + loadRecentConversations(); + }); + diff --git a/desktop/src/renderer/js/renderer.js b/desktop/src/renderer/js/renderer.js index eeb239c..a48e38a 100644 --- a/desktop/src/renderer/js/renderer.js +++ b/desktop/src/renderer/js/renderer.js @@ -4,57 +4,8 @@ console.log('Renderer process loaded'); // 当前选中的对话 let currentConversation = null; -// 对话数据(示例) -const conversations = { - miyu: { - id: 'miyu', - name: 'Miyu', - avatarColor: '#ff6b6b', - tags: ['愉快', '日常'], - messages: [ - { - id: 1, - sender: 'other', - text: '哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。', - timestamp: '2025-11-13 10:30:00' - }, - { - id: 2, - sender: 'user', - text: '听起来真不错!也许我们可以一起去?', - timestamp: '2025-11-13 10:31:00' - } - ] - }, - akira: { - id: 'akira', - name: 'Akira', - avatarColor: '#4ecdc4', - tags: ['紧张', '关键剧情'], - messages: [ - { - id: 1, - sender: 'other', - text: '别忘了我们的约定!', - timestamp: '2025-11-13 09:15:00' - } - ] - }, - hana: { - id: 'hana', - name: 'Hana', - avatarColor: '#ffe66d', - tags: ['愉快', '日常'], - messages: [ - { - id: 1, - sender: 'other', - text: '我今天发现了一家超可爱的小咖啡馆。', - timestamp: '2025-11-13 08:45:00' - } - ] - } -}; +// 对话数据(将从数据库加载) +let conversations = {}; // DOM加载完成后执行 document.addEventListener('DOMContentLoaded', () => { @@ -78,7 +29,6 @@ function setupHUDControl() { console.log('Show HUD button clicked'); if (window.electronAPI && window.electronAPI.showHUD) { window.electronAPI.showHUD(); - alert('HUD浮窗已打开(如果未显示,请检查日志)'); } else { console.error('electronAPI.showHUD not available'); alert('electronAPI.showHUD 不可用'); @@ -244,7 +194,7 @@ function renderMessages(messages) {

- ${msg.text} + ${msg.content || msg.text || ''}

+
+
+settings +

设置

+
+
+help_center +

帮助

+
+
+
+
+ +
+
+
+
+
+

与 Miyu 的对话*

+
+mood愉快 +local_florist日常 +
+
+
+
+ + + +
+
+
+
+
+ +
+auto_awesome +

AI分析报告

+
+expand_more +
+
+
+
+

表达能力

+

88

+
+
+star +star +star +star +star +
+

清晰流畅

+
+
+
+

话题选择

+

92

+
+
+star +star +star +star +star_half +
+

非常契合

+
+
+
+
+ +
+history_toggle_off +

关键时刻回放

+
+expand_more +
+
+
+
+
+
+

05:12

+

对方:“嗯,好像没什么特别的。”

+
+

AI评价: 回应过于平淡,错失了展开话题的机会。

+
+
+
+
+
+
+ +
+psychology +

对方性格分析

+
+expand_more +
+
+

内向、文艺、慢热。喜欢安静的氛围,对艺术和文学话题感兴趣。

+
+
+
+ +
+lightbulb +

行动建议

+
+expand_more +
+
+
    +
  • +check_circle +可以尝试的话题:最近读过的书、喜欢的音乐风格 +
  • +
  • +cancel +避开的话题:过于功利的现实问题 +
  • +
+
+
+
+
+
+
+
+

攻略对象

+
+

哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。

+
+ +
+
+
+
+
+
+

+
+

听起来真不错!也许我们可以一起去?

+
+ +
+
+
+
+
+
+
+
+

攻略对象

+
+

+我当然愿意! 我们在哪里见面好呢? +

+
+ +
+
+
+
+
+
+auto_awesome +

AI分析:积极回应!对方接受了你的邀请,表明好感度提升。

+
+
+mood愉快 +favorite心动 +
+
+
+
+ +
+
+
+
+ +
+ \ No newline at end of file diff --git a/desktop/src/db/database.js b/desktop/src/db/database.js index 8cbd833..c653f82 100644 --- a/desktop/src/db/database.js +++ b/desktop/src/db/database.js @@ -185,7 +185,7 @@ class DatabaseManager { LEFT JOIN messages m ON c.id = m.conversation_id WHERE c.character_id = ? GROUP BY c.id - ORDER BY c.updated_at DESC + ORDER BY c.created_at DESC `); return stmt.all(characterId); @@ -235,6 +235,31 @@ class DatabaseManager { return stmt.get(id); } + // 更新消息 + updateMessage(id, updates) { + const allowedFields = ['content']; + const updateFields = Object.keys(updates).filter(key => allowedFields.includes(key)); + + if (updateFields.length === 0) { + return this.getMessageById(id); + } + + const setClause = updateFields.map(field => `${field} = @${field}`).join(', '); + const stmt = this.db.prepare(` + UPDATE messages + SET ${setClause} + WHERE id = @id + `); + + const params = { id }; + updateFields.forEach(field => { + params[field] = updates[field]; + }); + + stmt.run(params); + return this.getMessageById(id); + } + // ========== 标签相关方法 ========== // 创建标签 @@ -321,6 +346,29 @@ class DatabaseManager { }; } + // 获取角色页面的统计数据 + getCharacterPageStatistics() { + // 总计攻略对象 + const characterCount = this.db.prepare('SELECT COUNT(*) as count FROM characters').get().count; + + // 活跃对话:两天内创建的新对话 + const twoDaysAgo = Date.now() - (2 * 24 * 60 * 60 * 1000); + const activeConversationCount = this.db.prepare(` + SELECT COUNT(*) as count + FROM conversations + WHERE created_at >= ? + `).get(twoDaysAgo).count; + + // 计算平均好感度 + const avgAffinity = this.db.prepare('SELECT AVG(affinity) as avg FROM characters').get().avg || 0; + + return { + characterCount, + activeConversationCount, + avgAffinity: Math.round(avgAffinity) + }; + } + // 获取最近对话(带角色信息) getRecentConversations(limit = 10) { const stmt = this.db.prepare(` @@ -354,7 +402,7 @@ class DatabaseManager { INNER JOIN characters char ON c.character_id = char.id LEFT JOIN messages m ON c.id = m.conversation_id GROUP BY c.id - ORDER BY c.updated_at DESC + ORDER BY c.created_at DESC `); return stmt.all(); @@ -371,6 +419,481 @@ class DatabaseManager { return stmt.all(conversationId); } + // ========== AI分析相关方法 ========== + + // 获取对话的AI分析报告 + getConversationAnalysis(conversationId) { + try { + const stmt = this.db.prepare(` + SELECT * FROM ai_analysis + WHERE conversation_id = ? AND insight_type = 'analysis_report' + ORDER BY created_at DESC + LIMIT 1 + `); + const result = stmt.get(conversationId); + console.log(`[DB] getConversationAnalysis for ${conversationId}:`, result ? 'found' : 'not found'); + if (result) { + console.log(`[DB] Analysis report content:`, result.content); + } + return result || null; + } catch (error) { + console.error('Error getting conversation analysis:', error); + return null; + } + } + + // 获取对话的关键时刻回放 + getKeyMoments(conversationId) { + try { + const stmt = this.db.prepare(` + SELECT + a.*, + m.content as message_content, + m.timestamp as message_timestamp, + m.sender + FROM ai_analysis a + LEFT JOIN messages m ON a.message_id = m.id + WHERE a.conversation_id = ? AND a.insight_type = 'key_moment' + ORDER BY a.created_at ASC + `); + return stmt.all(conversationId) || []; + } catch (error) { + console.error('Error getting key moments:', error); + return []; + } + } + + // 获取对话的行动建议 + getActionSuggestions(conversationId) { + try { + const stmt = this.db.prepare(` + SELECT * FROM ai_suggestions + WHERE conversation_id = ? + ORDER BY created_at DESC + `); + return stmt.all(conversationId) || []; + } catch (error) { + console.error('Error getting action suggestions:', error); + return []; + } + } + + // 获取对话的完整AI分析数据 + getConversationAIData(conversationId) { + console.log(`[DB] Getting AI data for conversation: ${conversationId}`); + + // 获取分析报告 + const analysisReport = this.getConversationAnalysis(conversationId); + console.log(`[DB] Analysis report found:`, analysisReport ? 'yes' : 'no'); + + // 获取关键时刻 + const keyMoments = this.getKeyMoments(conversationId); + console.log(`[DB] Key moments found: ${keyMoments.length}`); + + // 获取行动建议 + const actionSuggestions = this.getActionSuggestions(conversationId); + console.log(`[DB] Action suggestions found: ${actionSuggestions.length}`); + + // 获取对话信息以获取角色ID + const conversation = this.getConversationById(conversationId); + + // 获取本轮对话的表现态度分析(从ai_analysis表获取) + let attitudeAnalysis = null; + try { + const attitudeStmt = this.db.prepare(` + SELECT content FROM ai_analysis + WHERE conversation_id = ? AND insight_type = 'attitude_analysis' + ORDER BY created_at DESC + LIMIT 1 + `); + const attitudeData = attitudeStmt.get(conversationId); + if (attitudeData && attitudeData.content) { + // 如果content是JSON,解析它;否则直接使用 + try { + const parsed = JSON.parse(attitudeData.content); + const affinityChange = parsed.affinityChange || conversation?.affinity_change || 0; + attitudeAnalysis = { + description: parsed.description || parsed.content || attitudeData.content, + affinityChange: affinityChange, + trend: parsed.trend || (affinityChange > 0 ? '上升' : affinityChange < 0 ? '下降' : '持平') + }; + } catch (e) { + // 如果不是JSON,直接使用字符串,从conversation获取affinity_change + const affinityChange = conversation?.affinity_change || 0; + attitudeAnalysis = { + description: attitudeData.content, + affinityChange: affinityChange, + trend: affinityChange > 0 ? '上升' : affinityChange < 0 ? '下降' : '持平' + }; + } + } else if (conversation) { + // 如果没有专门的attitude_analysis,使用conversation的affinity_change作为基础 + const affinityChange = conversation.affinity_change || 0; + attitudeAnalysis = { + description: '本轮对话中,对方表现积极,互动良好。', + affinityChange: affinityChange, + trend: affinityChange > 0 ? '上升' : affinityChange < 0 ? '下降' : '持平' + }; + } + } catch (error) { + console.error('Error getting attitude analysis:', error); + } + + // 解析分析报告 + let parsedReport = null; + if (analysisReport && analysisReport.content) { + try { + parsedReport = JSON.parse(analysisReport.content); + } catch (e) { + console.error('Failed to parse analysis report:', e); + } + } + + // 解析关键时刻评价 + const parsedKeyMoments = keyMoments.map(km => { + let evaluation = null; + if (km.content) { + try { + evaluation = JSON.parse(km.content); + } catch (e) { + evaluation = km.content; // 如果不是JSON,直接使用字符串 + } + } + return { + id: km.id, + timestamp: km.message_timestamp, + messageContent: km.message_content, + sender: km.sender, + evaluation: evaluation + }; + }); + + const result = { + analysisReport: parsedReport, + keyMoments: parsedKeyMoments, + attitudeAnalysis, + actionSuggestions: actionSuggestions.map(as => ({ + id: as.id, + title: as.title, + content: as.content, + tags: as.tags ? as.tags.split(',').map(t => t.trim()) : [] + })) + }; + + console.log(`[DB] Returning AI data:`, { + hasAnalysisReport: !!result.analysisReport, + keyMomentsCount: result.keyMoments.length, + hasAttitudeAnalysis: !!result.attitudeAnalysis, + actionSuggestionsCount: result.actionSuggestions.length + }); + + return result; + } + + // ========== 角色详情相关方法 ========== + + // 获取角色详情 + getCharacterDetails(characterId) { + try { + const stmt = this.db.prepare('SELECT * FROM character_details WHERE character_id = ?'); + const row = stmt.get(characterId); + + if (!row) { + // 如果没有详情记录,尝试从会话中生成 + return this.generateCharacterDetailsFromConversations(characterId); + } + + // 解析JSON字段 + return { + character_id: row.character_id, + profile: row.profile ? JSON.parse(row.profile) : null, + personality_traits: row.personality_traits ? JSON.parse(row.personality_traits) : null, + likes_dislikes: row.likes_dislikes ? JSON.parse(row.likes_dislikes) : null, + important_events: row.important_events ? JSON.parse(row.important_events) : null, + conversation_summary: row.conversation_summary, + custom_fields: row.custom_fields ? JSON.parse(row.custom_fields) : {}, + updated_at: row.updated_at + }; + } catch (error) { + console.error('Error getting character details:', error); + return null; + } + } + + // 从会话中生成角色详情 + generateCharacterDetailsFromConversations(characterId) { + try { + // 获取角色的所有对话 + const conversations = this.getConversationsByCharacter(characterId); + + if (conversations.length === 0) { + return { + character_id: characterId, + profile: null, + personality_traits: null, + likes_dislikes: null, + important_events: null, + conversation_summary: '暂无对话记录', + custom_fields: {}, + updated_at: Date.now() + }; + } + + // 收集所有消息 + const allMessages = []; + const allSummaries = []; + const allTags = []; + const affinityChanges = []; + + for (const conv of conversations) { + const messages = this.getMessagesByConversation(conv.id); + allMessages.push(...messages); + + if (conv.summary) { + allSummaries.push(conv.summary); + } + + if (conv.tags) { + allTags.push(...conv.tags.split(',').map(t => t.trim())); + } + + if (conv.affinity_change) { + affinityChanges.push(conv.affinity_change); + } + } + + // 提取角色消息(sender = 'character') + const characterMessages = allMessages + .filter(msg => msg.sender === 'character') + .map(msg => msg.content); + + // 生成性格特点(从消息中提取关键词和模式) + const personalityTraits = this.extractPersonalityTraits(characterMessages, allTags); + + // 生成喜好厌恶(从消息中提取) + const likesDislikes = this.extractLikesDislikes(characterMessages); + + // 生成重要事件(从对话标题和摘要中提取) + const importantEvents = this.extractImportantEvents(conversations); + + // 生成对话总结 + const conversationSummary = this.generateConversationSummary(conversations, allSummaries, affinityChanges); + + // 生成角色档案(基本信息) + const character = this.getCharacterById(characterId); + const profile = character ? { + name: character.name, + nickname: character.nickname, + relationship_label: character.relationship_label, + affinity: character.affinity, + tags: character.tags || [], + created_at: character.created_at, + notes: character.notes + } : null; + + const details = { + character_id: characterId, + profile: profile, + personality_traits: personalityTraits, + likes_dislikes: likesDislikes, + important_events: importantEvents, + conversation_summary: conversationSummary, + custom_fields: {}, + updated_at: Date.now() + }; + + // 保存到数据库 + this.saveCharacterDetails(characterId, details); + + return details; + } catch (error) { + console.error('Error generating character details:', error); + return null; + } + } + + // 提取性格特点 + extractPersonalityTraits(messages, tags) { + const traits = { + keywords: [], + descriptions: [] + }; + + // 从标签中提取 + if (tags && tags.length > 0) { + traits.keywords = [...new Set(tags)]; + } + + // 从消息中分析(简单关键词匹配) + const traitKeywords = { + '温柔': ['温柔', '体贴', '关心', '照顾'], + '活泼': ['开心', '快乐', '兴奋', '活泼', '活跃'], + '认真': ['认真', '负责', '仔细', '专注'], + '内向': ['安静', '内向', '害羞', '沉默'], + '外向': ['外向', '开朗', '健谈', '热情'], + '幽默': ['有趣', '幽默', '搞笑', '玩笑'], + '真诚': ['真诚', '诚实', '真实', '坦率'] + }; + + const foundTraits = new Set(); + const messageText = messages.join(' '); + + for (const [trait, keywords] of Object.entries(traitKeywords)) { + if (keywords.some(keyword => messageText.includes(keyword))) { + foundTraits.add(trait); + } + } + + traits.keywords = [...new Set([...traits.keywords, ...foundTraits])]; + + // 生成描述 + if (traits.keywords.length > 0) { + traits.descriptions = [ + `从对话中可以看出,${traits.keywords.slice(0, 3).join('、')}是主要特点。`, + `在互动中表现出${traits.keywords[0]}的一面。` + ]; + } + + return traits; + } + + // 提取喜好厌恶 + extractLikesDislikes(messages) { + const likes = []; + const dislikes = []; + + const messageText = messages.join(' '); + + // 简单的关键词匹配(实际应用中可以使用更复杂的NLP) + const likeKeywords = ['喜欢', '爱好', '感兴趣', '爱', '享受', '享受', '享受']; + const dislikeKeywords = ['不喜欢', '讨厌', '厌恶', '反感', '不感兴趣']; + + // 提取包含"喜欢"的句子片段 + const likePatterns = messageText.match(/喜欢[^,。!?]*/g) || []; + likePatterns.forEach(pattern => { + const cleaned = pattern.replace(/喜欢/g, '').trim(); + if (cleaned && cleaned.length < 20) { + likes.push(cleaned); + } + }); + + // 提取包含"不喜欢"的句子片段 + const dislikePatterns = messageText.match(/不(喜欢|感兴趣)[^,。!?]*/g) || []; + dislikePatterns.forEach(pattern => { + const cleaned = pattern.replace(/不(喜欢|感兴趣)/g, '').trim(); + if (cleaned && cleaned.length < 20) { + dislikes.push(cleaned); + } + }); + + return { + likes: [...new Set(likes)].slice(0, 10), // 最多10个 + dislikes: [...new Set(dislikes)].slice(0, 10) + }; + } + + // 提取重要事件 + extractImportantEvents(conversations) { + const events = []; + + conversations.forEach(conv => { + if (conv.title || conv.summary) { + events.push({ + title: conv.title || '对话', + summary: conv.summary || '', + date: conv.date, + affinity_change: conv.affinity_change || 0 + }); + } + }); + + // 按日期排序,最新的在前 + events.sort((a, b) => b.date - a.date); + + return events.slice(0, 10); // 最多10个重要事件 + } + + // 生成对话总结 + generateConversationSummary(conversations, summaries, affinityChanges) { + const totalConversations = conversations.length; + const totalAffinityChange = affinityChanges.reduce((sum, change) => sum + change, 0); + const avgAffinityChange = affinityChanges.length > 0 + ? Math.round(totalAffinityChange / affinityChanges.length) + : 0; + + let summary = `共进行了 ${totalConversations} 次对话。`; + + if (summaries.length > 0) { + summary += `主要话题包括:${summaries.slice(0, 3).join('、')}。`; + } + + if (totalAffinityChange !== 0) { + const trend = totalAffinityChange > 0 ? '上升' : '下降'; + summary += `好感度总体${trend}了 ${Math.abs(totalAffinityChange)} 点。`; + } + + return summary; + } + + // 保存角色详情 + saveCharacterDetails(characterId, details) { + try { + const stmt = this.db.prepare(` + INSERT OR REPLACE INTO character_details + (character_id, profile, personality_traits, likes_dislikes, important_events, conversation_summary, custom_fields, updated_at) + VALUES (@character_id, @profile, @personality_traits, @likes_dislikes, @important_events, @conversation_summary, @custom_fields, @updated_at) + `); + + stmt.run({ + character_id: characterId, + profile: details.profile ? JSON.stringify(details.profile) : null, + personality_traits: details.personality_traits ? JSON.stringify(details.personality_traits) : null, + likes_dislikes: details.likes_dislikes ? JSON.stringify(details.likes_dislikes) : null, + important_events: details.important_events ? JSON.stringify(details.important_events) : null, + conversation_summary: details.conversation_summary || null, + custom_fields: details.custom_fields ? JSON.stringify(details.custom_fields) : '{}', + updated_at: details.updated_at || Date.now() + }); + + return true; + } catch (error) { + console.error('Error saving character details:', error); + return false; + } + } + + // 更新角色详情的自定义字段 + updateCharacterDetailsCustomFields(characterId, customFields) { + try { + const currentDetails = this.getCharacterDetails(characterId); + if (!currentDetails) { + return false; + } + + const updatedCustomFields = { + ...(currentDetails.custom_fields || {}), + ...customFields + }; + + const stmt = this.db.prepare(` + UPDATE character_details + SET custom_fields = @custom_fields, updated_at = @updated_at + WHERE character_id = @character_id + `); + + stmt.run({ + character_id: characterId, + custom_fields: JSON.stringify(updatedCustomFields), + updated_at: Date.now() + }); + + return true; + } catch (error) { + console.error('Error updating custom fields:', error); + return false; + } + } + // ========== 工具方法 ========== // 生成ID @@ -385,12 +908,22 @@ class DatabaseManager { // 检查对话数据是否存在 const conversationCount = this.db.prepare('SELECT COUNT(*) as count FROM conversations').get().count; const characterCount = this.db.prepare('SELECT COUNT(*) as count FROM characters').get().count; + const aiAnalysisCount = this.db.prepare('SELECT COUNT(*) as count FROM ai_analysis').get().count; - console.log(`Current database state: ${characterCount} characters, ${conversationCount} conversations`); + console.log(`Current database state: ${characterCount} characters, ${conversationCount} conversations, ${aiAnalysisCount} AI analyses`); - // 如果对话数据已存在,跳过 + // 如果对话数据已存在,检查是否需要插入AI分析数据 if (conversationCount > 0) { - console.log('Conversation data already exists, skipping seed...'); + // 如果AI分析数据不存在,只插入AI分析相关的数据 + if (aiAnalysisCount === 0) { + console.log('Conversation data exists but AI analysis data missing, inserting AI analysis data only...'); + this.seedAIDataOnly(); + } else { + console.log(`Conversation data already exists (${aiAnalysisCount} AI analyses found), skipping seed...`); + // 即使有数据,也检查一下是否有分析报告数据 + const reportCount = this.db.prepare('SELECT COUNT(*) as count FROM ai_analysis WHERE insight_type = ?').get('analysis_report').count; + console.log(`Found ${reportCount} analysis reports in database`); + } return; } @@ -507,6 +1040,298 @@ class DatabaseManager { // 不抛出错误,允许应用继续运行 } } + + // ========== LLM配置相关方法 ========== + + // 创建或更新LLM配置 + saveLLMConfig(configData) { + const now = Date.now(); + + // 如果设置为默认配置,先取消其他默认配置 + if (configData.is_default) { + const clearDefaultStmt = this.db.prepare('UPDATE llm_configs SET is_default = 0 WHERE is_default = 1'); + clearDefaultStmt.run(); + } + + // 检查是否已存在(通过id或name) + const existingStmt = this.db.prepare('SELECT * FROM llm_configs WHERE id = ? OR name = ?'); + const existing = existingStmt.get(configData.id || '', configData.name || ''); + + if (existing) { + // 更新现有配置 + const updateStmt = this.db.prepare(` + UPDATE llm_configs + SET name = @name, + provider = @provider, + api_key = @api_key, + base_url = @base_url, + is_default = @is_default, + updated_at = @updated_at + WHERE id = @id + `); + + updateStmt.run({ + id: existing.id, + name: configData.name || existing.name, + provider: configData.provider || existing.provider || 'openai', + api_key: configData.api_key || existing.api_key, + base_url: configData.base_url !== undefined ? configData.base_url : existing.base_url, + is_default: configData.is_default !== undefined ? (configData.is_default ? 1 : 0) : existing.is_default, + updated_at: now + }); + + return this.getLLMConfigById(existing.id); + } else { + // 创建新配置 + const insertStmt = this.db.prepare(` + INSERT INTO llm_configs (id, name, provider, api_key, base_url, is_default, created_at, updated_at) + VALUES (@id, @name, @provider, @api_key, @base_url, @is_default, @created_at, @updated_at) + `); + + const id = configData.id || this.generateId(); + insertStmt.run({ + id, + name: configData.name || '默认配置', + provider: configData.provider || 'openai', + api_key: configData.api_key, + base_url: configData.base_url || null, + is_default: configData.is_default ? 1 : 0, + created_at: now, + updated_at: now + }); + + return this.getLLMConfigById(id); + } + } + + // 获取所有LLM配置 + getAllLLMConfigs() { + const stmt = this.db.prepare('SELECT * FROM llm_configs ORDER BY is_default DESC, updated_at DESC'); + return stmt.all(); + } + + // 获取默认LLM配置 + getDefaultLLMConfig() { + const stmt = this.db.prepare('SELECT * FROM llm_configs WHERE is_default = 1 LIMIT 1'); + return stmt.get(); + } + + // 根据ID获取LLM配置 + getLLMConfigById(id) { + const stmt = this.db.prepare('SELECT * FROM llm_configs WHERE id = ?'); + return stmt.get(id); + } + + // 删除LLM配置 + deleteLLMConfig(id) { + const stmt = this.db.prepare('DELETE FROM llm_configs WHERE id = ?'); + return stmt.run(id); + } + + // 设置默认LLM配置 + setDefaultLLMConfig(id) { + // 先取消所有默认配置 + const clearDefaultStmt = this.db.prepare('UPDATE llm_configs SET is_default = 0 WHERE is_default = 1'); + clearDefaultStmt.run(); + + // 设置新的默认配置 + const setDefaultStmt = this.db.prepare('UPDATE llm_configs SET is_default = 1, updated_at = ? WHERE id = ?'); + setDefaultStmt.run(Date.now(), id); + + return this.getLLMConfigById(id); + } + + // 测试LLM连接(ping) + async testLLMConnection(configData) { + try { + // 动态导入openai(因为它是可选依赖) + const OpenAI = require('openai'); + + const config = { + apiKey: configData.api_key, + }; + + // 如果提供了base_url,使用自定义URL + if (configData.base_url) { + config.baseURL = configData.base_url; + } + + const client = new OpenAI(config); + + // 使用models.list()来测试连接(这是一个轻量级的API调用) + await client.models.list(); + + return { success: true, message: '连接成功' }; + } catch (error) { + console.error('LLM connection test failed:', error); + + // 提供更友好的错误信息 + let errorMessage = '连接失败'; + if (error.status === 401) { + errorMessage = 'API密钥无效'; + } else if (error.status === 404) { + errorMessage = 'API端点不存在'; + } else if (error.message) { + errorMessage = error.message; + } + + return { success: false, message: errorMessage, error: error.message }; + } + } + + // 只插入AI分析数据(当对话数据已存在但AI分析数据缺失时) + seedAIDataOnly() { + console.log('Seeding AI analysis data only...'); + + try { + const seedPath = path.join(__dirname, 'seed.sql'); + if (!fs.existsSync(seedPath)) { + console.warn('Seed SQL file not found, skipping AI data seeding'); + return; + } + + const seedSQL = fs.readFileSync(seedPath, 'utf-8'); + const lines = seedSQL.split('\n'); + let cleanedLines = []; + let currentStatement = ''; + + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + const originalLine = line; + + // 跳过空行 + if (!line.trim()) { + continue; + } + + // 跳过纯注释行(整行都是注释) + // 但如果currentStatement已经有内容,说明这是多行语句中的注释,应该跳过但不清空currentStatement + if (line.trim().startsWith('--')) { + continue; // 跳过注释行,但保留currentStatement + } + + // 移除行内注释(但保留SQL代码) + const commentIndex = line.indexOf('--'); + if (commentIndex >= 0) { + // 检查--是否在字符串内(简单检查) + const beforeComment = line.substring(0, commentIndex); + const singleQuotes = (beforeComment.match(/'/g) || []).length; + // 如果单引号数量是偶数,说明--不在字符串内,可以移除注释 + if (singleQuotes % 2 === 0) { + line = line.substring(0, commentIndex).trim(); + if (!line) continue; + } + } + + line = line.trim(); + if (!line) continue; + + // 累积到当前语句 + if (currentStatement) { + currentStatement += ' ' + line; + } else { + currentStatement = line; + } + + // 如果行以分号结尾,说明语句完整 + if (line.endsWith(';')) { + const statement = currentStatement.slice(0, -1).trim(); // 移除末尾的分号 + if (statement) { + // 只处理AI分析相关的INSERT语句 + const upperStatement = statement.toUpperCase(); + const isAIAnalysis = upperStatement.includes('INSERT') && upperStatement.includes('AI_ANALYSIS'); + const isAISuggestions = upperStatement.includes('INSERT') && upperStatement.includes('AI_SUGGESTIONS'); + + if (isAIAnalysis || isAISuggestions) { + cleanedLines.push(statement); + console.log(`[SQL Parser] Found AI statement (line ${i+1}): ${statement.substring(0, 150)}...`); + } + } + currentStatement = ''; + } + } + + if (currentStatement.trim()) { + const statement = currentStatement.trim(); + const upperStatement = statement.toUpperCase(); + if (upperStatement.includes('INSERT') && + (upperStatement.includes('INSERT INTO AI_ANALYSIS') || + upperStatement.includes('INSERT INTO AI_SUGGESTIONS'))) { + cleanedLines.push(statement); + console.log(`[SQL Parser] Found AI statement (final): ${statement.substring(0, 100)}...`); + } + } + + console.log(`Found ${cleanedLines.length} AI-related SQL statements to execute`); + + // 如果没找到,打印一些调试信息 + if (cleanedLines.length === 0) { + console.log('[SQL Parser] Debug: Checking seed.sql content...'); + const seedSQL = fs.readFileSync(path.join(__dirname, 'seed.sql'), 'utf-8'); + const hasAIAnalysis = seedSQL.includes('INSERT') && seedSQL.includes('ai_analysis'); + const hasAISuggestions = seedSQL.includes('INSERT') && seedSQL.includes('ai_suggestions'); + console.log(`[SQL Parser] seed.sql contains ai_analysis: ${hasAIAnalysis}, ai_suggestions: ${hasAISuggestions}`); + + // 尝试直接查找包含ai_analysis的行 + const lines = seedSQL.split('\n'); + let aiAnalysisLines = 0; + let aiSuggestionLines = 0; + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('ai_analysis')) aiAnalysisLines++; + if (lines[i].includes('ai_suggestions')) aiSuggestionLines++; + } + console.log(`[SQL Parser] Lines containing ai_analysis: ${aiAnalysisLines}, ai_suggestions: ${aiSuggestionLines}`); + } + + if (cleanedLines.length === 0) { + console.log('No AI analysis data found in seed file'); + return; + } + + // 打印前几个语句用于调试 + if (cleanedLines.length > 0) { + console.log('First statement preview:', cleanedLines[0].substring(0, 200) + '...'); + } + + const transaction = this.db.transaction(() => { + let successCount = 0; + let errorCount = 0; + for (let i = 0; i < cleanedLines.length; i++) { + const statement = cleanedLines[i]; + try { + this.db.exec(statement + ';'); + successCount++; + if (statement.includes('INSERT INTO ai_analysis')) { + console.log(`✓ Executed AI analysis INSERT statement ${i + 1}/${cleanedLines.length}`); + } else if (statement.includes('INSERT INTO ai_suggestions')) { + console.log(`✓ Executed AI suggestion INSERT statement ${i + 1}/${cleanedLines.length}`); + } + } catch (err) { + errorCount++; + if (err.message.includes('UNIQUE constraint') || err.message.includes('already exists')) { + console.log(`Statement ${i + 1}: skipped (duplicate)`); + } else { + console.error(`Error executing AI statement ${i + 1}:`, err.message); + console.error('Statement preview:', statement.substring(0, 200) + '...'); + } + } + } + console.log(`AI data insertion summary: ${successCount} succeeded, ${errorCount} errors`); + }); + + transaction(); + + // 验证数据插入 + const finalAICount = this.db.prepare('SELECT COUNT(*) as count FROM ai_analysis').get().count; + const finalSuggestionCount = this.db.prepare('SELECT COUNT(*) as count FROM ai_suggestions').get().count; + console.log(`AI data verification: ${finalAICount} AI analyses, ${finalSuggestionCount} AI suggestions`); + console.log('✅ AI analysis data seeding completed successfully'); + + } catch (error) { + console.error('Error seeding AI analysis data:', error); + console.error(error.stack); + } + } } module.exports = DatabaseManager; diff --git a/desktop/src/db/schema.sql b/desktop/src/db/schema.sql index d3b3d0f..93e83c3 100644 --- a/desktop/src/db/schema.sql +++ b/desktop/src/db/schema.sql @@ -81,6 +81,31 @@ CREATE TABLE IF NOT EXISTS ai_suggestions ( FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE ); +-- 角色详细信息表(从会话中总结) +CREATE TABLE IF NOT EXISTS character_details ( + character_id TEXT PRIMARY KEY, + profile TEXT, -- JSON格式:角色档案(基本信息、背景等) + personality_traits TEXT, -- JSON格式:性格特点(从对话中总结) + likes_dislikes TEXT, -- JSON格式:喜好厌恶(从对话中提取) + important_events TEXT, -- JSON格式:重要事件(从对话中提取) + conversation_summary TEXT, -- 对话总结 + custom_fields TEXT, -- JSON格式:自定义字段(可扩展) + updated_at INTEGER NOT NULL, + FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE +); + +-- LLM配置表 +CREATE TABLE IF NOT EXISTS llm_configs ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, -- 配置名称 + provider TEXT NOT NULL DEFAULT 'openai', -- 提供商(openai等) + api_key TEXT NOT NULL, -- API密钥 + base_url TEXT, -- API基础URL(可选,默认使用提供商的标准URL) + is_default INTEGER DEFAULT 0, -- 是否为默认配置 + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); + -- 索引优化 CREATE INDEX IF NOT EXISTS idx_conversations_character_id ON conversations(character_id); CREATE INDEX IF NOT EXISTS idx_conversations_date ON conversations(date); @@ -89,3 +114,5 @@ CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp); CREATE INDEX IF NOT EXISTS idx_character_tags_character_id ON character_tags(character_id); CREATE INDEX IF NOT EXISTS idx_ai_analysis_conversation_id ON ai_analysis(conversation_id); CREATE INDEX IF NOT EXISTS idx_ai_suggestions_conversation_id ON ai_suggestions(conversation_id); +CREATE INDEX IF NOT EXISTS idx_character_details_character_id ON character_details(character_id); +CREATE INDEX IF NOT EXISTS idx_llm_configs_is_default ON llm_configs(is_default); diff --git a/desktop/src/db/seed.sql b/desktop/src/db/seed.sql index 6e5cdaa..026dd13 100644 --- a/desktop/src/db/seed.sql +++ b/desktop/src/db/seed.sql @@ -87,3 +87,70 @@ INSERT OR IGNORE INTO ai_suggestions (id, conversation_id, message_id, title, co ('sugg_akira_1', 'conv_akira_2', 'msg_akira_2_2', '主动配合', '好的,我们一起看看哪些地方可以改进。', 8, '合作,积极', 1, 1735689702000), ('sugg_hana_1', 'conv_hana_2', 'msg_hana_2_2', '表达兴趣', '太好了!我也正好想找新书看。', 12, '兴趣,共鸣', 1, 1735689703000); +-- 插入AI分析报告数据 +INSERT OR IGNORE INTO ai_analysis (id, conversation_id, message_id, insight_type, content, created_at) VALUES +-- Miyu 对话1的分析报告 +('analysis_miyu_1', 'conv_miyu_1', NULL, 'analysis_report', '{"expressionAbility":{"score":88,"description":"清晰流畅"},"topicSelection":{"score":92,"description":"非常契合"}}', 1735689604000), +-- Miyu 对话2的分析报告 +('analysis_miyu_2', 'conv_miyu_2', NULL, 'analysis_report', '{"expressionAbility":{"score":85,"description":"自然亲切"},"topicSelection":{"score":90,"description":"话题合适"}}', 1735689704000), +-- Miyu 对话3的分析报告 +('analysis_miyu_3', 'conv_miyu_3', NULL, 'analysis_report', '{"expressionAbility":{"score":90,"description":"情感真挚"},"topicSelection":{"score":88,"description":"回忆共鸣"}}', 1735689804000), +-- Akira 对话1的分析报告 +('analysis_akira_1', 'conv_akira_1', NULL, 'analysis_report', '{"expressionAbility":{"score":82,"description":"条理清晰"},"topicSelection":{"score":85,"description":"工作相关"}}', 1735689604000), +-- Akira 对话2的分析报告 +('analysis_akira_2', 'conv_akira_2', NULL, 'analysis_report', '{"expressionAbility":{"score":86,"description":"表达准确"},"topicSelection":{"score":88,"description":"合作默契"}}', 1735689704000), +-- Akira 对话3的分析报告 +('analysis_akira_3', 'conv_akira_3', NULL, 'analysis_report', '{"expressionAbility":{"score":84,"description":"沟通有效"},"topicSelection":{"score":87,"description":"学习互助"}}', 1735689804000), +-- Hana 对话1的分析报告 +('analysis_hana_1', 'conv_hana_1', NULL, 'analysis_report', '{"expressionAbility":{"score":87,"description":"温和有礼"},"topicSelection":{"score":91,"description":"兴趣相投"}}', 1735689605000), +-- Hana 对话2的分析报告 +('analysis_hana_2', 'conv_hana_2', NULL, 'analysis_report', '{"expressionAbility":{"score":89,"description":"表达自然"},"topicSelection":{"score":93,"description":"非常契合"}}', 1735689705000), +-- Hana 对话3的分析报告 +('analysis_hana_3', 'conv_hana_3', NULL, 'analysis_report', '{"expressionAbility":{"score":88,"description":"情感细腻"},"topicSelection":{"score":90,"description":"氛围合适"}}', 1735689805000); + +-- 插入关键时刻回放数据 +INSERT OR IGNORE INTO ai_analysis (id, conversation_id, message_id, insight_type, content, created_at) VALUES +-- Miyu 对话1的关键时刻 +('keymoment_miyu_1_1', 'conv_miyu_1', 'msg_miyu_1_2', 'key_moment', '{"content":"回应积极,主动提出一起散步,推进了关系发展"}', 1735689601500), +-- Miyu 对话2的关键时刻 +('keymoment_miyu_2_1', 'conv_miyu_2', 'msg_miyu_2_2', 'key_moment', '{"content":"回应过于简单,可以展开更多话题"}', 1735689701500), +-- Akira 对话2的关键时刻 +('keymoment_akira_2_1', 'conv_akira_2', 'msg_akira_2_2', 'key_moment', '{"content":"主动配合工作,展现了合作精神,对方很满意"}', 1735689702500), +-- Hana 对话2的关键时刻 +('keymoment_hana_2_1', 'conv_hana_2', 'msg_hana_2_2', 'key_moment', '{"content":"表达了对书籍的兴趣,建立了共同话题,好感度提升"}', 1735689703500), +-- Hana 对话3的关键时刻 +('keymoment_hana_3_1', 'conv_hana_3', 'msg_hana_3_2', 'key_moment', '{"content":"回应过于平淡,错失了展开话题的机会"}', 1735689803500); + +-- 插入表现态度分析数据(针对本轮对话的) +INSERT OR IGNORE INTO ai_analysis (id, conversation_id, message_id, insight_type, content, created_at) VALUES +-- Miyu 对话1的表现态度分析 +('attitude_miyu_1', 'conv_miyu_1', NULL, 'attitude_analysis', '{"description":"对方在本轮对话中表现非常积极,主动提出一起散步的想法,对共同活动表现出浓厚的兴趣。整体态度友好热情,互动氛围轻松愉快。","affinityChange":10,"trend":"上升"}', 1735689604000), +-- Miyu 对话2的表现态度分析 +('attitude_miyu_2', 'conv_miyu_2', NULL, 'attitude_analysis', '{"description":"对方在偶遇时主动打招呼,表现出想要一起聊天的意愿。对话中态度友好,愿意分享时间和空间。","affinityChange":8,"trend":"上升"}', 1735689704000), +-- Miyu 对话3的表现态度分析 +('attitude_miyu_3', 'conv_miyu_3', NULL, 'attitude_analysis', '{"description":"对方主动提起童年回忆,表现出对过去美好时光的怀念。对话中情感真挚,愿意分享个人回忆,关系进一步加深。","affinityChange":12,"trend":"上升"}', 1735689804000), +-- Akira 对话1的表现态度分析 +('attitude_akira_1', 'conv_akira_1', NULL, 'attitude_analysis', '{"description":"对方在工作相关话题上表现专业认真,主动征求你的意见,展现出合作的态度。虽然话题较为正式,但互动积极。","affinityChange":5,"trend":"上升"}', 1735689604000), +-- Akira 对话2的表现态度分析 +('attitude_akira_2', 'conv_akira_2', NULL, 'attitude_analysis', '{"description":"对方对你的配合表示感谢,表现出对合作的认可。对话中态度友好,愿意共同解决问题,关系进一步改善。","affinityChange":7,"trend":"上升"}', 1735689704000), +-- Akira 对话3的表现态度分析 +('attitude_akira_3', 'conv_akira_3', NULL, 'attitude_analysis', '{"description":"对方主动关心你的学习情况,提出一起学习的建议,表现出互助的意愿。态度友好,愿意提供帮助。","affinityChange":6,"trend":"上升"}', 1735689804000), +-- Hana 对话1的表现态度分析 +('attitude_hana_1', 'conv_hana_1', NULL, 'attitude_analysis', '{"description":"对方在发现共同兴趣时表现出明显的兴奋,主动分享自己的阅读体验。虽然性格内向,但在感兴趣的话题上愿意主动交流。","affinityChange":8,"trend":"上升"}', 1735689604000), +-- Hana 对话2的表现态度分析 +('attitude_hana_2', 'conv_hana_2', NULL, 'attitude_analysis', '{"description":"对方主动推荐书籍,表现出想要分享和建立联系的意愿。对话中态度友好,愿意进一步交流,关系明显改善。","affinityChange":9,"trend":"上升"}', 1735689704000), +-- Hana 对话3的表现态度分析 +('attitude_hana_3', 'conv_hana_3', NULL, 'attitude_analysis', '{"description":"对方在安静的环境中表现出放松和舒适,愿意一起度过安静的时光。虽然对话简短,但态度友好,愿意继续接触。","affinityChange":7,"trend":"上升"}', 1735689804000); + +-- 插入行动建议数据(可以尝试的话题和避开的话题) +INSERT OR IGNORE INTO ai_suggestions (id, conversation_id, message_id, title, content, affinity_prediction, tags, is_used, created_at) VALUES +-- Miyu 的行动建议 +('action_miyu_1', 'conv_miyu_1', NULL, '可以尝试的话题', '最近读过的书、喜欢的音乐风格、户外活动、美食推荐', NULL, '可以尝试,话题', 0, 1735689604000), +('action_miyu_2', 'conv_miyu_1', NULL, '避开的话题', '过于功利的现实问题、工作压力、负面情绪', NULL, '避开,话题', 0, 1735689604000), +-- Akira 的行动建议 +('action_akira_1', 'conv_akira_1', NULL, '可以尝试的话题', '学习计划、未来规划、兴趣爱好、共同目标', NULL, '可以尝试,话题', 0, 1735689604000), +('action_akira_2', 'conv_akira_1', NULL, '避开的话题', '过于私人的问题、负面评价、抱怨', NULL, '避开,话题', 0, 1735689604000), +-- Hana 的行动建议 +('action_hana_1', 'conv_hana_1', NULL, '可以尝试的话题', '最近读过的书、喜欢的音乐风格、文学作品、安静的活动', NULL, '可以尝试,话题', 0, 1735689604000), +('action_hana_2', 'conv_hana_1', NULL, '避开的话题', '过于功利的现实问题、嘈杂的环境、过于活跃的话题', NULL, '避开,话题', 0, 1735689604000); + diff --git a/desktop/src/main.js b/desktop/src/main.js index cc4fdc7..7d323ce 100644 --- a/desktop/src/main.js +++ b/desktop/src/main.js @@ -20,9 +20,11 @@ function createWindow() { enableRemoteModule: false, preload: path.join(__dirname, 'preload.js') }, - titleBarStyle: 'default', + titleBarStyle: 'hidden', // 隐藏标题栏 show: false, // 先不显示,准备好后再显示 - title: 'LiveGalGame Desktop' + title: 'LiveGalGame Desktop', + // 无边框窗口 + frame: false }); // 加载index.html @@ -124,6 +126,64 @@ function setupIPC() { console.log('HUD拖拽结束'); }); + // 主窗口控制 + ipcMain.on('minimize-window', () => { + if (mainWindow) { + mainWindow.minimize(); + console.log('主窗口最小化'); + } + }); + + ipcMain.on('close-window', () => { + if (mainWindow) { + mainWindow.close(); + console.log('主窗口关闭'); + } + }); + + // 主窗口拖拽相关变量 + let mainDragStartPos = { x: 0, y: 0 }; + let mainDragWindowBounds = { x: 0, y: 0, width: 0, height: 0 }; + let isMainDragging = false; + + // 开始拖拽主窗口 + ipcMain.on('start-drag', (event, pos) => { + if (!mainWindow) return; + isMainDragging = true; + mainDragStartPos = pos; + // 获取窗口的完整边界信息 + const bounds = mainWindow.getBounds(); + mainDragWindowBounds = { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height + }; + console.log('主窗口拖拽开始,窗口边界:', mainDragWindowBounds); + }); + + // 更新主窗口拖拽位置 + ipcMain.on('update-drag', (event, pos) => { + if (!mainWindow || !isMainDragging) return; + const deltaX = pos.x - mainDragStartPos.x; + const deltaY = pos.y - mainDragStartPos.y; + const newX = mainDragWindowBounds.x + deltaX; + const newY = mainDragWindowBounds.y + deltaY; + // 使用setBounds同时设置位置和大小 + mainWindow.setBounds({ + x: newX, + y: newY, + width: mainDragWindowBounds.width, + height: mainDragWindowBounds.height + }); + }); + + // 结束主窗口拖拽 + ipcMain.on('end-drag', () => { + isMainDragging = false; + console.log('主窗口拖拽结束'); + }); + console.log('IPC通信已设置'); // 数据库IPC处理器 @@ -197,6 +257,20 @@ function setupIPC() { } }); + // 获取角色页面统计数据 + ipcMain.handle('db-get-character-page-statistics', () => { + try { + return db.getCharacterPageStatistics(); + } catch (error) { + console.error('Error getting character page statistics:', error); + return { + characterCount: 0, + activeConversationCount: 0, + avgAffinity: 0 + }; + } + }); + // 获取最近对话 ipcMain.handle('db-get-recent-conversations', (event, limit) => { try { @@ -217,6 +291,133 @@ function setupIPC() { } }); + // 更新消息 + ipcMain.handle('db-update-message', (event, messageId, updates) => { + try { + return db.updateMessage(messageId, updates); + } catch (error) { + console.error('Error updating message:', error); + return null; + } + }); + + // 获取对话的AI分析数据 + ipcMain.handle('db-get-conversation-ai-data', (event, conversationId) => { + try { + return db.getConversationAIData(conversationId); + } catch (error) { + console.error('Error getting conversation AI data:', error); + return { + analysisReport: null, + keyMoments: [], + personalityAnalysis: null, + actionSuggestions: [] + }; + } + }); + + // 获取角色详情 + ipcMain.handle('db-get-character-details', (event, characterId) => { + try { + return db.getCharacterDetails(characterId); + } catch (error) { + console.error('Error getting character details:', error); + return null; + } + }); + + // 更新角色详情的自定义字段 + ipcMain.handle('db-update-character-details-custom-fields', (event, characterId, customFields) => { + try { + return db.updateCharacterDetailsCustomFields(characterId, customFields); + } catch (error) { + console.error('Error updating character details custom fields:', error); + return false; + } + }); + + // 重新生成角色详情(从会话中) + ipcMain.handle('db-regenerate-character-details', (event, characterId) => { + try { + return db.generateCharacterDetailsFromConversations(characterId); + } catch (error) { + console.error('Error regenerating character details:', error); + return null; + } + }); + + // ========== LLM配置相关IPC处理器 ========== + + // 保存LLM配置 + ipcMain.handle('llm-save-config', (event, configData) => { + try { + return db.saveLLMConfig(configData); + } catch (error) { + console.error('Error saving LLM config:', error); + throw error; + } + }); + + // 获取所有LLM配置 + ipcMain.handle('llm-get-all-configs', () => { + try { + return db.getAllLLMConfigs(); + } catch (error) { + console.error('Error getting LLM configs:', error); + return []; + } + }); + + // 获取默认LLM配置 + ipcMain.handle('llm-get-default-config', () => { + try { + return db.getDefaultLLMConfig(); + } catch (error) { + console.error('Error getting default LLM config:', error); + return null; + } + }); + + // 获取指定ID的LLM配置 + ipcMain.handle('llm-get-config-by-id', (event, id) => { + try { + return db.getLLMConfigById(id); + } catch (error) { + console.error('Error getting LLM config:', error); + return null; + } + }); + + // 删除LLM配置 + ipcMain.handle('llm-delete-config', (event, id) => { + try { + return db.deleteLLMConfig(id); + } catch (error) { + console.error('Error deleting LLM config:', error); + throw error; + } + }); + + // 测试LLM连接 + ipcMain.handle('llm-test-connection', async (event, configData) => { + try { + return await db.testLLMConnection(configData); + } catch (error) { + console.error('Error testing LLM connection:', error); + return { success: false, message: error.message || '连接测试失败' }; + } + }); + + // 设置默认LLM配置 + ipcMain.handle('llm-set-default-config', (event, id) => { + try { + return db.setDefaultLLMConfig(id); + } catch (error) { + console.error('Error setting default LLM config:', error); + throw error; + } + }); + console.log('Database IPC handlers registered'); } diff --git a/desktop/src/preload.js b/desktop/src/preload.js index 822fe9a..288aad2 100644 --- a/desktop/src/preload.js +++ b/desktop/src/preload.js @@ -24,6 +24,15 @@ contextBridge.exposeInMainWorld('electronAPI', { updateHUDDrag: (pos) => ipcRenderer.send('update-hud-drag', pos), endHUDDrag: () => ipcRenderer.send('end-hud-drag'), + // 主窗口控制 + minimizeWindow: () => ipcRenderer.send('minimize-window'), + closeWindow: () => ipcRenderer.send('close-window'), + + // 窗口拖拽 + startDrag: (pos) => ipcRenderer.send('start-drag', pos), + updateDrag: (pos) => ipcRenderer.send('update-drag', pos), + endDrag: () => ipcRenderer.send('end-drag'), + // 数据库API getAllCharacters: () => ipcRenderer.invoke('db-get-all-characters'), getCharacterById: (id) => ipcRenderer.invoke('db-get-character-by-id', id), @@ -31,8 +40,23 @@ contextBridge.exposeInMainWorld('electronAPI', { getConversationsByCharacter: (characterId) => ipcRenderer.invoke('db-get-conversations-by-character', characterId), getMessagesByConversation: (conversationId) => ipcRenderer.invoke('db-get-messages-by-conversation', conversationId), getStatistics: () => ipcRenderer.invoke('db-get-statistics'), + getCharacterPageStatistics: () => ipcRenderer.invoke('db-get-character-page-statistics'), getRecentConversations: (limit) => ipcRenderer.invoke('db-get-recent-conversations', limit), - getAllConversations: () => ipcRenderer.invoke('db-get-all-conversations') + getAllConversations: () => ipcRenderer.invoke('db-get-all-conversations'), + updateMessage: (messageId, updates) => ipcRenderer.invoke('db-update-message', messageId, updates), + getConversationAIData: (conversationId) => ipcRenderer.invoke('db-get-conversation-ai-data', conversationId), + getCharacterDetails: (characterId) => ipcRenderer.invoke('db-get-character-details', characterId), + updateCharacterDetailsCustomFields: (characterId, customFields) => ipcRenderer.invoke('db-update-character-details-custom-fields', characterId, customFields), + regenerateCharacterDetails: (characterId) => ipcRenderer.invoke('db-regenerate-character-details', characterId), + + // LLM配置API + saveLLMConfig: (configData) => ipcRenderer.invoke('llm-save-config', configData), + getAllLLMConfigs: () => ipcRenderer.invoke('llm-get-all-configs'), + getDefaultLLMConfig: () => ipcRenderer.invoke('llm-get-default-config'), + getLLMConfigById: (id) => ipcRenderer.invoke('llm-get-config-by-id', id), + deleteLLMConfig: (id) => ipcRenderer.invoke('llm-delete-config', id), + testLLMConnection: (configData) => ipcRenderer.invoke('llm-test-connection', configData), + setDefaultLLMConfig: (id) => ipcRenderer.invoke('llm-set-default-config', id) }); // 监听主进程消息 diff --git a/desktop/src/renderer/characters.html b/desktop/src/renderer/characters.html index e1606b5..b62d367 100644 --- a/desktop/src/renderer/characters.html +++ b/desktop/src/renderer/characters.html @@ -31,7 +31,10 @@ @@ -87,21 +90,30 @@

+
总计攻略对象 groups
-
3
+
-
- 活跃对话 +
+ 活跃对话 +
+ + +
+
chat
-
5
+
-
@@ -109,7 +121,7 @@

平均好感度 favorite

-
68
+
-

@@ -125,6 +137,44 @@

+
+ +
+
+ account_circle +
+
+

查看详细信息

+

角色档案与对话总结

+
+ +
+ + +
+ +
+
+

加载中...

+
+
+ + +
+ + +
+
+ + diff --git a/desktop/src/renderer/conversation-editor.html b/desktop/src/renderer/conversation-editor.html index 23e9a26..d2a35c0 100644 --- a/desktop/src/renderer/conversation-editor.html +++ b/desktop/src/renderer/conversation-editor.html @@ -65,7 +65,7 @@

- @@ -94,10 +94,6 @@

settings

设置

-
- help_center -

帮助

-
@@ -150,17 +146,16 @@

- - +
+ +
+ +
-
- + +
+ +
@@ -168,11 +163,11 @@

-

AI 分析与编辑

+

对话总览

- +
-

AI 洞察

+

复盘分析

auto_awesome @@ -189,22 +184,6 @@

- - -
- -
-

-
`; }).join(''); + // 绑定编辑事件 + container.querySelectorAll('.message-edit-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const messageId = btn.dataset.messageId; + startEditMessage(messageId); + }); + }); + + container.querySelectorAll('.message-save-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const messageId = btn.dataset.messageId; + saveMessage(messageId); + }); + }); + + container.querySelectorAll('.message-cancel-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const messageId = btn.dataset.messageId; + cancelEditMessage(messageId); + }); + }); + // 滚动到底部 container.scrollTop = container.scrollHeight; } + // 开始编辑消息 + function startEditMessage(messageId) { + const messageItem = document.querySelector(`[data-message-id="${messageId}"]`); + if (!messageItem) return; + + const contentEl = messageItem.querySelector('.message-content'); + const editEl = messageItem.querySelector('.message-edit'); + const editBtn = messageItem.querySelector('.message-edit-btn'); + const actionsEl = messageItem.querySelector('.message-actions'); + + if (contentEl && editEl && editBtn && actionsEl) { + contentEl.classList.add('hidden'); + editEl.classList.remove('hidden'); + editBtn.classList.add('hidden'); + actionsEl.classList.remove('hidden'); + + // 设置编辑框宽度和样式 + editEl.style.width = contentEl.offsetWidth + 'px'; + editEl.style.minHeight = contentEl.offsetHeight + 'px'; + editEl.style.maxWidth = '100%'; + + editEl.focus(); + // 将光标移到文本末尾 + editEl.setSelectionRange(editEl.value.length, editEl.value.length); + + // 自动调整高度 + editEl.style.height = 'auto'; + editEl.style.height = Math.max(editEl.scrollHeight, contentEl.offsetHeight) + 'px'; + } + } + + // 保存消息 + async function saveMessage(messageId) { + const messageItem = document.querySelector(`[data-message-id="${messageId}"]`); + if (!messageItem) return; + + const editEl = messageItem.querySelector('.message-edit'); + if (!editEl) return; + + const newContent = editEl.value.trim(); + if (!newContent) { + alert('消息内容不能为空'); + return; + } + + try { + if (window.electronAPI && window.electronAPI.updateMessage) { + await window.electronAPI.updateMessage(messageId, { content: newContent }); + + // 更新显示 + const contentEl = messageItem.querySelector('.message-content'); + if (contentEl) { + contentEl.textContent = newContent; + } + + // 退出编辑模式 + cancelEditMessage(messageId); + } else { + console.error('Update message API not available'); + alert('更新消息失败:API不可用'); + } + } catch (error) { + console.error('Failed to update message:', error); + alert('更新消息失败:' + error.message); + } + } + + // 取消编辑消息 + function cancelEditMessage(messageId) { + const messageItem = document.querySelector(`[data-message-id="${messageId}"]`); + if (!messageItem) return; + + const contentEl = messageItem.querySelector('.message-content'); + const editEl = messageItem.querySelector('.message-edit'); + const editBtn = messageItem.querySelector('.message-edit-btn'); + const actionsEl = messageItem.querySelector('.message-actions'); + + if (contentEl && editEl && editBtn && actionsEl) { + // 恢复原始内容 + const originalContent = contentEl.textContent; + editEl.value = originalContent; + + contentEl.classList.remove('hidden'); + editEl.classList.add('hidden'); + editBtn.classList.remove('hidden'); + actionsEl.classList.add('hidden'); + } + } + // 加载对话列表 async function loadConversations() { try { @@ -428,36 +927,11 @@

-

还没有对话记录

-

- `; - return; - } - + // 初始化过滤列表 + filteredConversations = [...allConversations]; + // 渲染对话列表 - allConversations.forEach(conv => { - const item = document.createElement('div'); - item.className = 'conversation-item flex items-center gap-3 px-3 py-2 rounded-full hover:bg-surface-light dark:hover:bg-surface-dark cursor-pointer'; - item.dataset.conversationId = conv.id; - - item.innerHTML = ` -
-
-

${conv.title || '无标题对话'}

-

${conv.character_name || ''} · ${conv.message_count || 0} 条消息

-
- `; - - item.addEventListener('click', () => { - selectConversation(conv.id); - }); - - conversationList.appendChild(item); - }); + renderConversationList(); // 如果有指定的对话ID,打开该对话;否则自动选择第一个 if (conversationId) { @@ -476,10 +950,57 @@

+

${allConversations.length === 0 ? '还没有对话记录' : '没有找到匹配的对话'}

+

+ `; + return; + } + + conversationList.innerHTML = ''; + + filteredConversations.forEach(conv => { + const item = document.createElement('div'); + item.className = 'conversation-item flex items-center gap-3 px-3 py-2 rounded-full hover:bg-surface-light dark:hover:bg-surface-dark cursor-pointer'; + item.dataset.conversationId = conv.id; + + const dateText = formatDate(conv.created_at); + + item.innerHTML = ` +
+
+

${conv.title || '无标题对话'}

+

${conv.character_name || ''} · ${conv.message_count || 0} 条消息 · ${dateText}

+
+ `; + + item.addEventListener('click', () => { + selectConversation(conv.id); + }); + + conversationList.appendChild(item); + }); + } + // 页面加载时初始化 document.addEventListener('DOMContentLoaded', () => { console.log('Conversation editor loaded'); loadConversations(); + + // 设置搜索框事件监听 + const searchInput = document.getElementById('search-input'); + if (searchInput) { + searchInput.addEventListener('input', (e) => { + filterConversations(e.target.value); + }); + } }); diff --git a/desktop/src/renderer/index.html b/desktop/src/renderer/index.html index a2ef7b5..e2094cd 100644 --- a/desktop/src/renderer/index.html +++ b/desktop/src/renderer/index.html @@ -89,28 +89,12 @@

LiveGalGame

history

历史对话

- - developer_board -

LLM 配置

-
settings

设置

- - - @@ -261,7 +245,7 @@

最近对话< const dateText = daysAgo === 0 ? '今天' : daysAgo === 1 ? '昨天' : `${daysAgo}天前`; return ` -
+
@@ -277,13 +261,10 @@

${conv.ti

${conv.summary || '暂无摘要'}

-
+

最后编辑:${dateText}

-
-
+
`; }).join('') + ` diff --git a/desktop/src/renderer/js/renderer.js b/desktop/src/renderer/js/renderer.js index a48e38a..6769973 100644 --- a/desktop/src/renderer/js/renderer.js +++ b/desktop/src/renderer/js/renderer.js @@ -67,8 +67,6 @@ function setupNavigation() { // 显示即将推出的提示 if (page === 'characters') { alert('攻略对象管理功能即将推出!'); - } else if (page === 'llm') { - alert('LLM配置功能即将推出!'); } else if (page === 'settings') { alert('设置功能即将推出!'); } @@ -336,7 +334,7 @@ function updateAIInsights(conversationId) { `).join(''); container.innerHTML = ` -

AI 洞察

+

复盘分析

${insightsHTML}
diff --git a/desktop/src/renderer/settings.html b/desktop/src/renderer/settings.html index b19eb91..190c21f 100644 --- a/desktop/src/renderer/settings.html +++ b/desktop/src/renderer/settings.html @@ -14,6 +14,30 @@ min-height: 100vh; } + .back-link:hover { + gap: 12px; + } + + .error-message { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.2); + color: #ef4444; + padding: 12px; + border-radius: 8px; + margin-top: 12px; + font-size: 14px; + } + + .success-message { + background: rgba(34, 197, 94, 0.1); + border: 1px solid rgba(34, 197, 94, 0.2); + color: #22c55e; + padding: 12px; + border-radius: 8px; + margin-top: 12px; + font-size: 14px; + } + .dark body { background: linear-gradient(135deg, #1a0a12 0%, #0d0510 100%); } @@ -192,7 +216,7 @@
- + ← 返回 @@ -238,25 +262,46 @@

- 🔑 API配置 + 🤖 LLM配置 +
+ +
+ + +
+ +
+ +
- +
- - + +
+
+ +
+ +
+
- - + +

@@ -284,6 +329,7 @@

{ + messageDiv.remove(); + }, 5000); + } + + // 保存LLM配置 + llmSaveBtn.addEventListener('click', async () => { + try { + const apiKey = llmApiKey.value.trim(); + if (!apiKey) { + showLLMMessage('error', '请输入API Key'); + return; + } + + llmSaveBtn.disabled = true; + llmSaveBtn.textContent = '保存中...'; + + const configData = { + id: currentLLMConfig?.id, + name: llmConfigName.value.trim() || '默认配置', + provider: llmProvider.value, + api_key: apiKey, + base_url: llmBaseUrl.value.trim() || null, + is_default: llmIsDefault.checked ? 1 : 0 + }; + + const savedConfig = await window.electronAPI.saveLLMConfig(configData); + currentLLMConfig = savedConfig; + + showLLMMessage('success', '配置已保存'); + llmSaveBtn.textContent = '保存配置'; + llmSaveBtn.disabled = false; + } catch (error) { + console.error('Failed to save LLM config:', error); + showLLMMessage('error', '保存失败:' + (error.message || '未知错误')); + llmSaveBtn.textContent = '保存配置'; + llmSaveBtn.disabled = false; + } + }); + + // 测试LLM连接 + llmTestBtn.addEventListener('click', async () => { + try { + const apiKey = llmApiKey.value.trim(); + if (!apiKey) { + showLLMMessage('error', '请输入API Key'); + return; + } + + llmTestBtn.disabled = true; + llmTestBtn.textContent = '测试中...'; + + const configData = { + provider: llmProvider.value, + api_key: apiKey, + base_url: llmBaseUrl.value.trim() || null + }; + + const result = await window.electronAPI.testLLMConnection(configData); + + if (result.success) { + showLLMMessage('success', result.message || '连接成功!'); + } else { + showLLMMessage('error', result.message || '连接失败'); + } + + llmTestBtn.textContent = '测试连接'; + llmTestBtn.disabled = false; + } catch (error) { + console.error('Failed to test LLM connection:', error); + showLLMMessage('error', '测试失败:' + (error.message || '未知错误')); + llmTestBtn.textContent = '测试连接'; + llmTestBtn.disabled = false; + } + }); + + + // 页面加载时初始化 + document.addEventListener('DOMContentLoaded', () => { + checkMicrophonePermission(); + loadLLMConfig(); + }); + console.log('Settings page loaded'); From 8a5c6fbaca934a74aed07379dced8c5a336ac42d Mon Sep 17 00:00:00 2001 From: chenspeculation Date: Sun, 16 Nov 2025 22:06:32 +0800 Subject: [PATCH 030/147] feat: refactor desktop application structure and enhance development setup - Updated package.json scripts for improved development workflow using Vite and Electron. - Introduced new configuration files for PostCSS and Tailwind CSS to enhance styling capabilities. - Added Vite configuration for better build management and performance. - Created new React components and pages for improved application structure, including Overview, Characters, Conversation Editor, and Settings. - Removed outdated HTML files and replaced them with React components for a more modern approach. - Enhanced HUD functionality with a new React-based implementation. - Updated main process to support dynamic loading of HUD in development and production environments. - Removed obsolete documentation and files to streamline the project. --- desktop/package.json | 39 +- desktop/pnpm-lock.yaml | 1747 +++++++++++++++++ desktop/postcss.config.js | 7 + desktop/spec/UI_DESIGN_INDEX.md | 300 --- desktop/spec/audio-capture-tech-note.md | 54 - desktop/spec/build-and-release.md | 51 - desktop/spec/data-model.md | 58 - desktop/spec/hud-ux.md | 33 - .../70433c63813d57abeb67ff85363cbc0c.jpg | Bin 95173 -> 0 bytes .../70753aea0271777b0d92bfa4fb3ad015.jpg | Bin 71497 -> 0 bytes .../9fc9fa48e8988b3eb496f07c45b7a6b6.jpg | Bin 111185 -> 0 bytes .../cfef12820f837d316c204595222899b9.jpg | Bin 119624 -> 0 bytes .../images/llm\351\205\215\347\275\256.html" | 210 -- .../images/\344\270\273\351\241\265.html" | 230 --- .../images/\345\257\271\350\257\235.html" | 349 ---- ...\350\257\235\351\241\265\351\235\242.html" | 251 --- ...\345\201\234\345\212\251\346\211\213.html" | 182 -- desktop/spec/llm-integration.md | 34 - desktop/spec/prd-desktop.md | 60 - desktop/spec/privacy-and-permissions.md | 30 - desktop/spec/tech-architecture.md | 57 - desktop/spec/test-plan.md | 38 - desktop/spec/ui-design-01-chat-window.md | 460 ----- desktop/spec/ui-design-02-llm-config.md | 577 ------ desktop/spec/ui-design-03-dashboard.md | 557 ------ .../spec/ui-design-04-conversation-detail.md | 614 ------ desktop/spec/ui-design-components.md | 1001 ---------- desktop/src/main.js | 47 +- desktop/src/renderer/App.jsx | 24 + desktop/src/renderer/characters.html | 812 -------- desktop/src/renderer/components/Layout.jsx | 112 ++ desktop/src/renderer/conversation-editor.html | 1007 ---------- desktop/src/renderer/hud.css | 246 +++ desktop/src/renderer/hud.html | 427 +--- desktop/src/renderer/hud.jsx | 208 ++ desktop/src/renderer/index.css | 40 + desktop/src/renderer/index.html | 279 +-- desktop/src/renderer/main.jsx | 14 + desktop/src/renderer/pages/Characters.jsx | 581 ++++++ .../src/renderer/pages/ConversationEditor.jsx | 520 +++++ desktop/src/renderer/pages/Overview.jsx | 271 +++ desktop/src/renderer/pages/Settings.jsx | 144 ++ desktop/src/renderer/settings.html | 714 ------- desktop/tailwind.config.js | 44 + desktop/vite.config.js | 35 + 45 files changed, 4068 insertions(+), 8396 deletions(-) create mode 100644 desktop/postcss.config.js delete mode 100644 desktop/spec/UI_DESIGN_INDEX.md delete mode 100644 desktop/spec/audio-capture-tech-note.md delete mode 100644 desktop/spec/build-and-release.md delete mode 100644 desktop/spec/data-model.md delete mode 100644 desktop/spec/hud-ux.md delete mode 100644 desktop/spec/images/70433c63813d57abeb67ff85363cbc0c.jpg delete mode 100644 desktop/spec/images/70753aea0271777b0d92bfa4fb3ad015.jpg delete mode 100644 desktop/spec/images/9fc9fa48e8988b3eb496f07c45b7a6b6.jpg delete mode 100644 desktop/spec/images/cfef12820f837d316c204595222899b9.jpg delete mode 100644 "desktop/spec/images/llm\351\205\215\347\275\256.html" delete mode 100644 "desktop/spec/images/\344\270\273\351\241\265.html" delete mode 100644 "desktop/spec/images/\345\257\271\350\257\235.html" delete mode 100644 "desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" delete mode 100644 "desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" delete mode 100644 desktop/spec/llm-integration.md delete mode 100644 desktop/spec/prd-desktop.md delete mode 100644 desktop/spec/privacy-and-permissions.md delete mode 100644 desktop/spec/tech-architecture.md delete mode 100644 desktop/spec/test-plan.md delete mode 100644 desktop/spec/ui-design-01-chat-window.md delete mode 100644 desktop/spec/ui-design-02-llm-config.md delete mode 100644 desktop/spec/ui-design-03-dashboard.md delete mode 100644 desktop/spec/ui-design-04-conversation-detail.md delete mode 100644 desktop/spec/ui-design-components.md create mode 100644 desktop/src/renderer/App.jsx delete mode 100644 desktop/src/renderer/characters.html create mode 100644 desktop/src/renderer/components/Layout.jsx delete mode 100644 desktop/src/renderer/conversation-editor.html create mode 100644 desktop/src/renderer/hud.css create mode 100644 desktop/src/renderer/hud.jsx create mode 100644 desktop/src/renderer/index.css create mode 100644 desktop/src/renderer/main.jsx create mode 100644 desktop/src/renderer/pages/Characters.jsx create mode 100644 desktop/src/renderer/pages/ConversationEditor.jsx create mode 100644 desktop/src/renderer/pages/Overview.jsx create mode 100644 desktop/src/renderer/pages/Settings.jsx delete mode 100644 desktop/src/renderer/settings.html create mode 100644 desktop/tailwind.config.js create mode 100644 desktop/vite.config.js diff --git a/desktop/package.json b/desktop/package.json index 9ae55b3..730cb5d 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -4,10 +4,16 @@ "description": "LiveGalGame Desktop - 实时对话辅助与学习工具", "main": "src/main.js", "scripts": { - "dev": "electron . --enable-logging", - "build": "electron-builder", - "build:win": "electron-builder --win", - "build:mac": "electron-builder --mac" + "dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && cross-env NODE_ENV=development electron . --enable-logging\"", + "dev:vite": "vite", + "dev:electron": "electron . --enable-logging", + "build": "vite build && electron-builder", + "build:vite": "vite build", + "build:win": "vite build && electron-builder --win", + "build:mac": "vite build && electron-builder --mac", + "preview": "vite preview", + "start:prod": "vite build && cross-env NODE_ENV=production electron .", + "start:client": "vite build && cross-env NODE_ENV=production electron ." }, "keywords": [ "electron", @@ -18,13 +24,26 @@ "author": "LiveGalGame Team", "license": "MIT", "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "autoprefixer": "^10.4.22", + "concurrently": "^9.1.0", + "cross-env": "^10.1.0", "electron": "^33.2.0", - "electron-builder": "^25.1.8" + "electron-builder": "^25.1.8", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.18", + "vite": "^6.0.5", + "wait-on": "^8.0.1" }, "dependencies": { "better-sqlite3": "^12.4.1", "electron-store": "^8.2.0", - "openai": "^6.9.0" + "openai": "^6.9.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" }, "build": { "appId": "com.livegalgame.desktop", @@ -33,9 +52,11 @@ "output": "dist" }, "files": [ - "src/**/*", - "assets/**/*", - "node_modules/**/*" + "dist/renderer/**/*", + "src/main.js", + "src/preload.js", + "src/db/**/*", + "package.json" ], "mac": { "category": "public.app-category.productivity" diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index d81867b..a8c7d11 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -17,19 +17,145 @@ importers: openai: specifier: ^6.9.0 version: 6.9.0 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^6.28.0 + version: 6.30.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@types/react': + specifier: ^18.3.12 + version: 18.3.26 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.7(@types/react@18.3.26) + '@vitejs/plugin-react': + specifier: ^4.3.3 + version: 4.7.0(vite@6.4.1(@types/node@24.10.1)(jiti@1.21.7)) + autoprefixer: + specifier: ^10.4.22 + version: 10.4.22(postcss@8.5.6) + concurrently: + specifier: ^9.1.0 + version: 9.2.1 + cross-env: + specifier: ^10.1.0 + version: 10.1.0 electron: specifier: ^33.2.0 version: 33.4.11 electron-builder: specifier: ^25.1.8 version: 25.1.8(electron-builder-squirrel-windows@25.1.8) + postcss: + specifier: ^8.5.6 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.18 + version: 3.4.18 + vite: + specifier: ^6.0.5 + version: 6.4.1(@types/node@24.10.1)(jiti@1.21.7) + wait-on: + specifier: ^8.0.1 + version: 8.0.5 packages: 7zip-bin@5.2.0: resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==} + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@develar/schema-utils@2.6.5': resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} @@ -61,9 +187,188 @@ packages: resolution: {integrity: sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==} engines: {node: '>=16.4'} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.4': + resolution: {integrity: sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==} + engines: {node: '>=14.0.0'} + + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -76,6 +381,22 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@malept/cross-spawn-promise@2.0.0': resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} engines: {node: '>= 12.13.0'} @@ -84,6 +405,18 @@ packages: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@npmcli/fs@2.1.2': resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -97,10 +430,130 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@remix-run/router@1.23.1': + resolution: {integrity: sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==} + engines: {node: '>=14.0.0'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.53.2': + resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.2': + resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.2': + resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.2': + resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.2': + resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.2': + resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.2': + resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.2': + resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.2': + resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.2': + resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.2': + resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.2': + resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.2': + resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.2': + resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.2': + resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.2': + resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.2': + resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.2': + resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==} + cpu: [x64] + os: [win32] + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@szmarczak/http-timer@4.0.6': resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -109,12 +562,27 @@ packages: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} @@ -136,6 +604,17 @@ packages: '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.26': + resolution: {integrity: sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==} + '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -145,6 +624,12 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@xmldom/xmldom@0.8.11': resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} @@ -203,6 +688,13 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + app-builder-bin@5.0.0-alpha.10: resolution: {integrity: sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==} @@ -233,6 +725,9 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -262,16 +757,34 @@ packages: resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} engines: {node: '>=10.12.0'} + autoprefixer@10.4.22: + resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.8.28: + resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} + hasBin: true + better-sqlite3@12.4.1: resolution: {integrity: sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==} engines: {node: 20.x || 22.x || 23.x || 24.x} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -294,6 +807,15 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -326,10 +848,21 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -386,6 +919,10 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} @@ -401,6 +938,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concurrently@9.2.1: + resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} + engines: {node: '>=18'} + hasBin: true + conf@10.2.0: resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} engines: {node: '>=12'} @@ -411,6 +953,9 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -429,10 +974,23 @@ packages: crc@3.8.0: resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.0: + resolution: {integrity: sha512-si++xzRAY9iPp60roQiFta7OFbhrgvcthrhlNAGeQptSY25uJjkfUV8OArC3KLocB8JT8ohz+qgxWCmz8RhjIg==} + debounce-fn@4.0.0: resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} engines: {node: '>=10'} @@ -483,9 +1041,15 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dmg-builder@25.1.8: resolution: {integrity: sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==} @@ -533,6 +1097,9 @@ packages: electron-store@8.2.0: resolution: {integrity: sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==} + electron-to-chromium@1.5.252: + resolution: {integrity: sha512-53uTpjtRgS7gjIxZ4qCgFdNO2q+wJt/Z8+xAvxbCqXPJrY6h7ighUkadQmNMXH96crtpa6gPFNP7BF4UBGDuaA==} + electron@33.4.11: resolution: {integrity: sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==} engines: {node: '>= 12.20.55'} @@ -576,6 +1143,11 @@ packages: es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -603,25 +1175,54 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -630,6 +1231,9 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -656,6 +1260,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -664,6 +1273,10 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -683,6 +1296,14 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -804,14 +1425,30 @@ packages: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -819,6 +1456,10 @@ packages: is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -849,10 +1490,26 @@ packages: engines: {node: '>=10'} hasBin: true + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + joi@18.0.1: + resolution: {integrity: sha512-IiQpRyypSnLisQf3PwuN2eIHAsAIGZIrLZkd4zdvIar2bDyhM91ubRjy8a3eYablXsh9BeI/c7dmPYHca5qtoA==} + engines: {node: '>= 20'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -889,6 +1546,13 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -915,6 +1579,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -922,6 +1590,9 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -942,6 +1613,14 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -1036,6 +1715,14 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -1058,6 +1745,9 @@ packages: engines: {node: ^12.13 || ^14.13 || >=16} hasBin: true + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + nopt@6.0.0: resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -1067,6 +1757,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -1076,6 +1770,14 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -1142,6 +1844,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -1156,6 +1861,22 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} @@ -1164,6 +1885,53 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} @@ -1188,6 +1956,9 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -1195,6 +1966,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -1203,10 +1977,39 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-router-dom@6.30.2: + resolution: {integrity: sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.2: + resolution: {integrity: sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + read-binary-file-arch@1.0.6: resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} hasBin: true + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -1217,6 +2020,10 @@ packages: readdir-glob@1.1.3: resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1232,6 +2039,11 @@ packages: resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + responselike@2.0.1: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} @@ -1243,6 +2055,10 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -1252,6 +2068,17 @@ packages: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} + rollup@4.53.2: + resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -1267,6 +2094,9 @@ packages: sax@1.4.3: resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -1294,6 +2124,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -1327,6 +2161,10 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -1371,6 +2209,11 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -1379,6 +2222,19 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.18: + resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==} + engines: {node: '>=14.0.0'} + hasBin: true + tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} @@ -1393,6 +2249,17 @@ packages: temp-file@3.4.0: resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} @@ -1400,9 +2267,23 @@ packages: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -1441,6 +2322,12 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -1454,6 +2341,51 @@ packages: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} engines: {node: '>=0.6.0'} + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + wait-on@8.0.5: + resolution: {integrity: sha512-J3WlS0txVHkhLRb2FsmRg3dkMTCV1+M6Xra3Ho7HzZDHpE7DCOnoSoCJsZotrmW3uRMhvIJGSKUKrh/MeF4iag==} + engines: {node: '>=12.0.0'} + hasBin: true + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -1484,6 +2416,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -1510,6 +2445,120 @@ snapshots: 7zip-bin@5.2.0: {} + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@develar/schema-utils@2.6.5': dependencies: ajv: 6.12.6 @@ -1586,8 +2635,104 @@ snapshots: transitivePeerDependencies: - supports-color + '@epic-web/invariant@1.0.0': {} + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + '@gar/promisify@1.1.3': {} + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.4': {} + + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -1603,6 +2748,25 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@malept/cross-spawn-promise@2.0.0': dependencies: cross-spawn: 7.0.6 @@ -1616,6 +2780,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 @@ -1629,14 +2805,107 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@remix-run/router@1.23.1': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.53.2': + optional: true + + '@rollup/rollup-android-arm64@4.53.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.2': + optional: true + + '@rollup/rollup-darwin-x64@4.53.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.2': + optional: true + '@sindresorhus/is@4.6.0': {} + '@standard-schema/spec@1.0.0': {} + '@szmarczak/http-timer@4.0.6': dependencies: defer-to-connect: 2.0.1 '@tootallnate/once@2.0.0': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.5 + '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 @@ -1648,6 +2917,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/estree@1.0.8': {} + '@types/fs-extra@9.0.13': dependencies: '@types/node': 24.10.1 @@ -1674,6 +2945,17 @@ snapshots: xmlbuilder: 15.1.1 optional: true + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.26)': + dependencies: + '@types/react': 18.3.26 + + '@types/react@18.3.26': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.0 + '@types/responselike@1.0.3': dependencies: '@types/node': 20.19.25 @@ -1686,6 +2968,18 @@ snapshots: '@types/node': 20.19.25 optional: true + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@24.10.1)(jiti@1.21.7))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1(@types/node@24.10.1)(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + '@xmldom/xmldom@0.8.11': {} abbrev@1.1.1: {} @@ -1739,6 +3033,13 @@ snapshots: ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + app-builder-bin@5.0.0-alpha.10: {} app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): @@ -1824,6 +3125,8 @@ snapshots: delegates: 1.0.0 readable-stream: 3.6.2 + arg@5.0.2: {} + argparse@2.0.1: {} assert-plus@1.0.0: @@ -1842,15 +3145,37 @@ snapshots: atomically@1.7.0: {} + autoprefixer@10.4.22(postcss@8.5.6): + dependencies: + browserslist: 4.28.0 + caniuse-lite: 1.0.30001754 + fraction.js: 5.3.4 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + balanced-match@1.0.2: {} base64-js@1.5.1: {} + baseline-browser-mapping@2.8.28: {} + better-sqlite3@12.4.1: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 + binary-extensions@2.3.0: {} + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -1879,6 +3204,18 @@ snapshots: dependencies: balanced-match: 1.0.2 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.8.28 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.252 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) + buffer-crc32@0.2.13: {} buffer-from@1.1.2: {} @@ -1956,11 +3293,27 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001754: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chownr@1.1.4: {} chownr@2.0.0: {} @@ -2007,6 +3360,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@4.1.1: {} + commander@5.1.0: {} compare-version@0.1.2: {} @@ -2020,6 +3375,15 @@ snapshots: concat-map@0.0.1: {} + concurrently@9.2.1: + dependencies: + chalk: 4.1.2 + rxjs: 7.8.2 + shell-quote: 1.8.3 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + conf@10.2.0: dependencies: ajv: 8.17.1 @@ -2040,6 +3404,8 @@ snapshots: console-control-strings@1.1.0: {} + convert-source-map@2.0.0: {} + core-util-is@1.0.2: optional: true @@ -2057,12 +3423,21 @@ snapshots: buffer: 5.7.1 optional: true + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + cssesc@3.0.0: {} + + csstype@3.2.0: {} + debounce-fn@4.0.0: dependencies: mimic-fn: 3.1.0 @@ -2106,11 +3481,15 @@ snapshots: detect-node@2.1.0: optional: true + didyoumean@1.2.2: {} + dir-compare@4.2.0: dependencies: minimatch: 3.1.2 p-limit: 3.1.0 + dlv@1.1.3: {} + dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): dependencies: app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) @@ -2205,6 +3584,8 @@ snapshots: conf: 10.2.0 type-fest: 2.19.0 + electron-to-chromium@1.5.252: {} + electron@33.4.11: dependencies: '@electron/get': 2.0.3 @@ -2248,6 +3629,35 @@ snapshots: es6-error@4.1.1: optional: true + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + escalade@3.2.0: {} escape-string-regexp@4.0.0: @@ -2272,24 +3682,46 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-uri@3.1.0: {} + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + fd-slicer@1.1.0: dependencies: pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-uri-to-path@1.0.0: {} filelist@1.0.4: dependencies: minimatch: 5.1.6 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-up@3.0.0: dependencies: locate-path: 3.0.0 + follow-redirects@1.15.11: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -2303,6 +3735,8 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + fraction.js@5.3.4: {} + fs-constants@1.0.0: {} fs-extra@10.1.0: @@ -2336,6 +3770,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} gauge@4.0.4: @@ -2349,6 +3786,8 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -2375,6 +3814,14 @@ snapshots: github-from-package@0.0.0: {} + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@10.4.5: dependencies: foreground-child: 3.3.1 @@ -2527,16 +3974,32 @@ snapshots: ip-address@10.1.0: {} + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-ci@3.0.1: dependencies: ci-info: 3.9.0 + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + is-interactive@1.0.0: {} is-lambda@1.0.1: {} + is-number@7.0.0: {} + is-obj@2.0.0: {} is-unicode-supported@0.1.0: {} @@ -2561,10 +4024,26 @@ snapshots: filelist: 1.0.4 picocolors: 1.1.1 + jiti@1.21.7: {} + + joi@18.0.1: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.4 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.0.0 + + js-tokens@4.0.0: {} + js-yaml@4.1.1: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -2598,6 +4077,10 @@ snapshots: dependencies: readable-stream: 2.3.8 + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + locate-path@3.0.0: dependencies: p-locate: 3.0.0 @@ -2620,10 +4103,18 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -2659,6 +4150,13 @@ snapshots: math-intrinsics@1.1.0: {} + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + mime-db@1.52.0: {} mime-types@2.1.35: @@ -2736,6 +4234,14 @@ snapshots: ms@2.1.3: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + napi-build-utils@2.0.0: {} negotiator@0.6.4: {} @@ -2768,12 +4274,16 @@ snapshots: - bluebird - supports-color + node-releases@2.0.27: {} + nopt@6.0.0: dependencies: abbrev: 1.1.1 normalize-path@3.0.0: {} + normalize-range@0.1.2: {} + normalize-url@6.1.0: {} npmlog@6.0.2: @@ -2783,6 +4293,10 @@ snapshots: gauge: 4.0.4 set-blocking: 2.0.0 + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + object-keys@1.1.1: optional: true @@ -2836,6 +4350,8 @@ snapshots: path-key@3.1.1: {} + path-parse@1.0.7: {} + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -2847,6 +4363,14 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + pkg-up@3.1.0: dependencies: find-up: 3.0.0 @@ -2857,6 +4381,43 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prebuild-install@7.1.3: dependencies: detect-libc: 2.1.2 @@ -2883,6 +4444,8 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + proxy-from-env@1.1.0: {} + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -2890,6 +4453,8 @@ snapshots: punycode@2.3.1: {} + queue-microtask@1.2.3: {} + quick-lru@5.1.1: {} rc@1.2.8: @@ -2899,12 +4464,40 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.17.0: {} + + react-router-dom@6.30.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.2(react@18.3.1) + + react-router@6.30.2(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.1 + react: 18.3.1 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + read-binary-file-arch@1.0.6: dependencies: debug: 4.4.3 transitivePeerDependencies: - supports-color + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -2925,6 +4518,10 @@ snapshots: dependencies: minimatch: 5.1.6 + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -2935,6 +4532,12 @@ snapshots: resolve-alpn@1.2.1: {} + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + responselike@2.0.1: dependencies: lowercase-keys: 2.0.0 @@ -2946,6 +4549,8 @@ snapshots: retry@0.12.0: {} + reusify@1.1.0: {} + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -2960,6 +4565,42 @@ snapshots: sprintf-js: 1.1.3 optional: true + rollup@4.53.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.2 + '@rollup/rollup-android-arm64': 4.53.2 + '@rollup/rollup-darwin-arm64': 4.53.2 + '@rollup/rollup-darwin-x64': 4.53.2 + '@rollup/rollup-freebsd-arm64': 4.53.2 + '@rollup/rollup-freebsd-x64': 4.53.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.2 + '@rollup/rollup-linux-arm-musleabihf': 4.53.2 + '@rollup/rollup-linux-arm64-gnu': 4.53.2 + '@rollup/rollup-linux-arm64-musl': 4.53.2 + '@rollup/rollup-linux-loong64-gnu': 4.53.2 + '@rollup/rollup-linux-ppc64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-musl': 4.53.2 + '@rollup/rollup-linux-s390x-gnu': 4.53.2 + '@rollup/rollup-linux-x64-gnu': 4.53.2 + '@rollup/rollup-linux-x64-musl': 4.53.2 + '@rollup/rollup-openharmony-arm64': 4.53.2 + '@rollup/rollup-win32-arm64-msvc': 4.53.2 + '@rollup/rollup-win32-ia32-msvc': 4.53.2 + '@rollup/rollup-win32-x64-gnu': 4.53.2 + '@rollup/rollup-win32-x64-msvc': 4.53.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -2972,6 +4613,10 @@ snapshots: sax@1.4.3: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + semver-compare@1.0.0: optional: true @@ -2992,6 +4637,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.3: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -3030,6 +4677,8 @@ snapshots: ip-address: 10.1.0 smart-buffer: 4.2.0 + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -3076,6 +4725,16 @@ snapshots: strip-json-comments@2.0.1: {} + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + sumchecker@3.0.1: dependencies: debug: 4.4.3 @@ -3086,6 +4745,40 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.18: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.0 + transitivePeerDependencies: + - tsx + - yaml + tar-fs@2.1.4: dependencies: chownr: 1.1.4 @@ -3115,16 +4808,39 @@ snapshots: async-exit-hook: 2.0.1 fs-extra: 10.1.0 + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tmp-promise@3.0.3: dependencies: tmp: 0.2.5 tmp@0.2.5: {} + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tree-kill@1.2.2: {} + truncate-utf8-bytes@1.0.2: dependencies: utf8-byte-length: 1.0.5 + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -3152,6 +4868,12 @@ snapshots: universalify@2.0.1: {} + update-browserslist-db@1.1.4(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -3167,6 +4889,29 @@ snapshots: extsprintf: 1.4.1 optional: true + vite@6.4.1(@types/node@24.10.1)(jiti@1.21.7): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.2 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.1 + fsevents: 2.3.3 + jiti: 1.21.7 + + wait-on@8.0.5: + dependencies: + axios: 1.13.2 + joi: 18.0.1 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -3197,6 +4942,8 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yallist@4.0.0: {} yargs-parser@21.1.1: {} diff --git a/desktop/postcss.config.js b/desktop/postcss.config.js new file mode 100644 index 0000000..c21c076 --- /dev/null +++ b/desktop/postcss.config.js @@ -0,0 +1,7 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; + diff --git a/desktop/spec/UI_DESIGN_INDEX.md b/desktop/spec/UI_DESIGN_INDEX.md deleted file mode 100644 index 94c26ce..0000000 --- a/desktop/spec/UI_DESIGN_INDEX.md +++ /dev/null @@ -1,300 +0,0 @@ -# 桌面端 UI 设计文档索引 - -> **项目**:LiveGalGame 桌面版 -> **平台**:Electron(Windows + macOS) -> **设计系统**:完整的倒推提示词(Reverse Prompt),用于指导 LLM 精确开发界面 - ---- - -## 📋 文档结构 - -### 核心 UI 规范文档 - -| 文档名 | 覆盖页面 | 优先级 | 大小 | -|-------|---------|-------|------| -| **ui-design-01-chat-window.md** | 对话 HUD 浮窗 | P0 | ~15KB | -| **ui-design-02-llm-config.md** | AI 模型配置管理 | P1 | ~18KB | -| **ui-design-03-dashboard.md** | 主页/仪表板 | P1 | ~16KB | -| **ui-design-04-conversation-detail.md** | 对话详情与 AI 分析 | P1 | ~20KB | -| **ui-design-components.md** | 可复用组件库 | P1 | ~25KB | - -### 相关技术文档 - -| 文档名 | 功能 | 链接 | -|-------|------|------| -| **prd-desktop.md** | 产品需求 | 核心用户旅程 | -| **tech-architecture.md** | 系统架构 | Electron 分层设计 | -| **audio-capture-tech-note.md** | 音频采集 | 麦克风 + 系统音频 | -| **llm-integration.md** | LLM 集成 | 模型接口与提示工程 | -| **hud-ux.md** | HUD 交互 | 浮窗交互细节 | -| **data-model.md** | 数据模型 | SQLite 表设计 | -| **build-and-release.md** | 构建发布 | Win/Mac 打包流程 | - ---- - -## 🎯 快速导航 - -### 场景 1:我要开发聊天 HUD 浮窗 - -**快速路径**: -1. 📖 阅读 `ui-design-01-chat-window.md`(完整的 UI 规范) -2. 📖 参考 `ui-design-components.md` 中的 MessageBubble、SuggestionCard 组件 -3. 🛠️ 使用提供的 Tailwind CSS 颜色变量和 Framer Motion 动画预设 -4. ✅ 对照第 12 节验收标准进行自测 - -**关键代码片段位置**: -- 消息气泡:第 3 节 -- 推荐卡片:第 4 节 -- 状态指示器:第 5 节 -- 底部操作栏:第 6 节 - ---- - -### 场景 2:我要开发 LLM 配置页面 - -**快速路径**: -1. 📖 阅读 `ui-design-02-llm-config.md` -2. 📖 参考 `ui-design-components.md` 中的 Card、Button、Input 组件 -3. 🛠️ 模态框使用 Framer Motion scaleIn 预设 -4. ✅ 对照第 12 节验收标准 - -**关键区域**: -- 侧边栏导航:第 2 节 -- 模型卡片列表:第 4 节 -- 添加模型对话框:第 5 节 - ---- - -### 场景 3:我要开发主页/仪表板 - -**快速路径**: -1. 📖 阅读 `ui-design-03-dashboard.md` -2. 📖 参考 `ui-design-components.md` 中的 StatCard、ConversationCard 组件 -3. 🛠️ 对话卡片使用 container + item stagger 动画 -4. ✅ 对照第 15 节验收标准 - -**关键区域**: -- 统计卡片网格:第 4 节 -- 对话卡片列表:第 6 节 -- 新建对话模态框:第 8 节 - ---- - -### 场景 4:我要开发对话详情与分析页面 - -**快速路径**: -1. 📖 阅读 `ui-design-04-conversation-detail.md` -2. 📖 参考消息气泡(重用 01 文档)和分析卡片规范 -3. 🛠️ 右侧分析板使用 slideInRight 动画 -4. ✅ 对照第 15 节验收标准 - -**关键区域**: -- 对话内容区:第 4 节 -- 右侧分析板:第 6 节 -- 消息编辑对话框:第 8 节 - ---- - -### 场景 5:我要构建组件库并实现代码复用 - -**快速路径**: -1. 📖 阅读 `ui-design-components.md` 全文 -2. 🛠️ 按照第 2-4 节创建基础组件、容器组件、业务组件 -3. 📝 在 Storybook 中编写每个组件的 Story(第 12 节示例) -4. ✅ 编写 Jest 测试用例(第 9 节) - -**目录结构**: -``` -src/components/ -├── base/ -│ ├── Button.tsx -│ ├── Input.tsx -│ ├── Card.tsx -│ ├── Badge.tsx -│ └── Tag.tsx -├── containers/ -│ ├── Layout.tsx -│ ├── Sidebar.tsx -│ └── Header.tsx -├── features/ -│ ├── MessageBubble.tsx -│ ├── SuggestionCard.tsx -│ ├── StatCard.tsx -│ └── ConversationCard.tsx -├── hooks/ -│ ├── useTheme.ts -│ └── useUIStore.ts -└── lib/ - ├── animations.ts - └── tokens.ts -``` - ---- - -## 🎨 设计令牌快速参考 - -### 颜色 -``` -品牌粉红:#D91B5C -深粉红:#C2185B -浅粉红:rgba(217, 27, 92, 0.1) - -中性灰:#6B7280 ~ #1F2937 -浅灰:#E5E7EB ~ #F9FAFB - -状态绿:#10B981 -状态红:#EF4444 -状态黄:#F59E0B -状态蓝:#3B82F6 -``` - -### 间距系统 -``` -4px (xs) → 8px (sm) → 16px (md) → 24px (lg) → 32px (xl) → 40px (2xl) -``` - -### 圆角 -``` -4px (xs) → 6px (sm) → 8px (md) → 12px (lg) → 16px (xl) -``` - -### 阴影 -``` -xs: 0 1px 2px rgba(0, 0, 0, 0.05) -sm: 0 1px 3px rgba(0, 0, 0, 0.1) -md: 0 4px 6px rgba(0, 0, 0, 0.1) -lg: 0 10px 15px rgba(0, 0, 0, 0.15) -xl: 0 10px 40px rgba(0, 0, 0, 0.2) -``` - ---- - -## 🚀 开发工作流 - -### 第一步:环境搭建 -```bash -# 安装依赖 -npm install react react-dom framer-motion zustand react-query - -# Electron 相关 -npm install electron electron-builder - -# UI 工具 -npm install @tailwindcss/forms autoprefixer postcss tailwindcss - -# 开发工具 -npm install -D @testing-library/react @testing-library/jest-dom jest storybook -``` - -### 第二步:创建 Tailwind 配置 -参考 `ui-design-components.md` 第 1.1 节的设计令牌,配置 `tailwind.config.js` - -### 第三步:创建组件 -按优先级: -1. 基础组件(Button, Input, Card, Badge) -2. 容器组件(Layout, Sidebar, Header) -3. 业务组件(MessageBubble, SuggestionCard 等) - -### 第四步:集成到页面 -- 先做 Chat Window(P0) -- 再做 Dashboard、LLM Config(P1) -- 最后做 Conversation Detail(P1 低优先级) - -### 第五步:测试与优化 -- 功能测试(对照验收标准) -- 性能测试(60fps 帧率) -- 跨平台测试(Win + macOS) - ---- - -## 📐 页面尺寸与响应式 - -### 桌面版断点 -- **≥ 1400px**:完整三列或两列布局 -- **1000px ~ 1399px**:压缩模式 -- **< 1000px**:单列或模态弹出(用于小屏模式) - -### 窗口最小尺寸 -- **主窗口**:1200px × 800px -- **Chat HUD**:440px × 600px~800px -- **对话框**:400px~800px - ---- - -## 🎬 动画与交互预设 - -### 常用动画(Framer Motion) -- **fadeInOut**:淡入淡出(200ms) -- **slideUp**:从下滑入(300ms) -- **slideInRight**:从右滑入(300ms) -- **scaleIn**:缩放 + 淡入(200ms) -- **container + item**:Stagger 列表(50ms 间隔) - -### 按钮交互 -- Hover:背景色变化 + 指针变手指 -- Active:缩放 0.95 + 快速恢复 -- Disabled:opacity 0.5 + cursor not-allowed -- Loading:旋转动画 + 文字变为"加载中..." - ---- - -## ✅ 验收清单 - -### 开发完成时,需检查: -- [ ] 所有按钮、输入框、卡片样式与设计文档一致 -- [ ] 动画流畅(60fps,无卡顿) -- [ ] 文字对比度符合 WCAG AA 标准(≥ 4.5:1) -- [ ] 键盘快捷键正常工作 -- [ ] 屏幕阅读器友好(ARIA 标签完整) -- [ ] Windows 和 macOS 显示一致 -- [ ] 响应式布局在不同尺寸正常工作 - ---- - -## 📖 设计文档维护 - -### 文档修改流程 -1. 在 Figma 中修改设计 -2. 更新对应的 MD 文档(含截图或伪代码) -3. 更新 CHANGELOG(版本号 + 变更说明) -4. 通知前端团队进行代码同步 - -### 文档版本 -- **v1.0**:初版(2025-11-12) -- 未来版本:需要时更新 - ---- - -## 🔗 相关资源链接 - -### 在线工具 -- Figma 设计文件:(待提供链接) -- Storybook 在线预览:http://localhost:6006 -- Tailwind Color Palette:https://tailwindcss.com/docs/customizing-colors - -### 参考文档 -- Tailwind CSS 文档:https://tailwindcss.com/docs -- Framer Motion 文档:https://www.framer.com/motion/ -- Electron 文档:https://www.electronjs.org/docs -- React 最佳实践:https://react.dev/ - -### 外部组件库(可选集成) -- **UI 框架**:Shadcn UI、Ant Design、Material-UI -- **图表库**:Recharts、Chart.js、ECharts -- **表格**:TanStack Table(React Table) -- **表单**:React Hook Form、Formik - ---- - -## 👥 联系方式 - -- **设计**:[产品设计团队] -- **前端**:[前端工程团队] -- **反馈**:在 GitHub Issues 或团队 Slack 频道提出 - ---- - -**文档最后更新**:2025-11-12 -**维护者**:产品与技术团队 -**许可证**:Internal Use Only - diff --git a/desktop/spec/audio-capture-tech-note.md b/desktop/spec/audio-capture-tech-note.md deleted file mode 100644 index 2bfdc4f..0000000 --- a/desktop/spec/audio-capture-tech-note.md +++ /dev/null @@ -1,54 +0,0 @@ -音频捕获技术方案(麦克风 + 系统音频) - -目标 -- 同时捕获用户麦克风与对方(应用)系统音频,进行双通道转写与上下文分析。 -- 在 Windows 与 macOS 上以 Electron 提供一致体验。 - -方案总览 -1) 麦克风:标准 getUserMedia({ audio: true }),设备可选;回声消除/降噪开启。 -2) 系统音频: - - Windows:Electron desktopCapturer 选择 Entire Screen + audio:true(底层走 WASAPI Loopback)。 - - macOS:desktopCapturer 捕获屏幕并勾选系统音频(需屏幕录制权限);若某些版本无系统音轨,则引导安装虚拟声卡(如 BlackHole)并将其设为输出-输入桥。 -3) 合并/分发:将 micStream 与 sysStream 分发到各自 ASR 管线;必要时做对齐与去混叠。 - -权限与提示 -- Windows:首次无需显式系统音频权限;仅麦克风权限提示。 -- macOS:需要“屏幕录制”权限以捕获系统音频轨,以及“麦克风”权限;需文案解释用途。 - -时序与数据流 -- 设备枚举 → 选择设备 → 启动 micStream 与 sysStream → VAD/分段 → ASR(本地或远端)→ 渲染器显示转写。 -- 采样率统一至 16k/24k(与 ASR 兼容),采用 WebAudio 或 AudioWorklet 做重采样。 - -示例(伪代码) - -```ts -// 获取麦克风 -const mic = await navigator.mediaDevices.getUserMedia({ - audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } -}); - -// 获取系统音频(带屏幕) -const sources = await desktopCapturer.getSources({ types: ['screen'], fetchWindowIcons: false }); -const sys = await navigator.mediaDevices.getUserMedia({ - audio: { mandatory: { chromeMediaSource: 'desktop' } }, - video: { - mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: sources[0].id } - } -}); - -// 仅使用音轨 -const sysAudio = new MediaStream(sys.getAudioTracks()); - -// 分发到 ASR -asr.consume('mic', mic); -asr.consume('sys', sysAudio); -``` - -性能与稳定性 -- 将捕获与编码/重采样放在 AudioWorklet 或 Worker,避免阻塞 UI。 -- 控制内存与延迟:每段 2-5 秒;启用背压丢弃过期片段。 - -降级策略 -- macOS 若无法获取系统音轨:引导用户选择虚拟声卡输出;或只启用麦克风通道(提示功能受限)。 - - diff --git a/desktop/spec/build-and-release.md b/desktop/spec/build-and-release.md deleted file mode 100644 index f0a6ad1..0000000 --- a/desktop/spec/build-and-release.md +++ /dev/null @@ -1,51 +0,0 @@ -构建、签名与发布(Windows/macOS) - -工具链 -- electron-builder 24+(建议)或 electron-forge。本文以 electron-builder 为例。 -- Node.js 20+,pnpm。 - -目录与脚本(建议) -- package.json - - "dev": 同时启动 main 与 renderer,启用热重载 - - "build:win": electron-builder --win nsis - - "build:mac": electron-builder --mac dmg - -electron-builder 配置要点 -- appId: com.livegalgame.desktop -- asar: true -- files: dist/**, build/** -- extraResources: 语音/模型/词典(如有) -- mac: - - category: public.app-category.productivity - - hardenedRuntime: true - - entitlements: build/mac/entitlements.plist - - entitlementsInherit: build/mac/entitlements.plist - - gatekeeperAssess: false - - notarize: 使用 Apple ID/Keychain(CI 中配置 APPLE_ID 等) -- win: - - target: nsis - - publisherName: 组织/公司名称 - - signingHashAlgorithms: ["sha256"] - - rfc3161TimeStampServer: http://timestamp.digicert.com - -权限与 Entitlements(macOS) -- 需要:麦克风(com.apple.security.device.audio-input) -- 屏幕录制:不在 Entitlements 配置,而在系统“隐私与安全性”里由应用触发时请求 -- 可选:辅助功能(用于全局快捷键/前台检测,通常不必) - -CI/CD(建议) -- GitHub Actions / GitLab CI: - - 矩阵构建:macos-latest + windows-latest - - 缓存 node_modules;签名证书和 Apple Notarization 凭据用 Secrets - - 工件:.dmg、.exe(或 .msi)、RELEASES -- 自动更新:electron-updater + 静态文件托管(GitHub Releases/自建 CDN) - -验证清单(出厂前) -- 安装成功;首次启动权限请求文案正确;音频测试可用。 -- HUD 能置顶且不抢焦点;Win/mac 均可捕获系统音频(或提供降级指引)。 -- 签名有效;macOS 可通过 Gatekeeper;可完成 Notarization。 - -网络与加速 -- 下载外部资源(ASR/LLM 相关权重/词典)时,支持代理与断点续传。可建议用户运行 `dl1` 以加速,并使用多进程/分片并发。 - - diff --git a/desktop/spec/data-model.md b/desktop/spec/data-model.md deleted file mode 100644 index 4dd800f..0000000 --- a/desktop/spec/data-model.md +++ /dev/null @@ -1,58 +0,0 @@ -数据模型与存储(SQLite) - -存储引擎 -- 本地 SQLite(better-sqlite3);加密字段(API Key)存 OS Keychain/DPAPI。 -- 迁移机制:按版本号执行 DDL;备份与导入/导出 JSON。 - -表设计(简版) -- person - - id (pk, uuid) - - name (text) - - relation_tag (text) // 暧昧对象/同事/好友等 - - notes (text) // 备注,供提示工程 - - favor_score (int) // 当前好感度 0-100 - - created_at, updated_at -- conversation - - id (pk) - - person_id (fk) - - started_at, ended_at - - meta (json) // 设备、模型、环境信息 -- turn - - id (pk) - - conversation_id (fk) - - role (text) // user/peer - - text (text) // 转写结果 - - start_ms, end_ms - - source (text) // mic/system -- event - - id (pk) - - conversation_id (fk) - - type (text) // suggestion_shown/accepted/feedback - - payload (json) // 建议卡内容、被采纳与否、打分等 - - ts -- score - - id (pk) - - conversation_id (fk) - - delta (int) // +20/-5 - - reason (text) - - ts -- model_profile - - id (pk) - - provider (text) // openai/anthropic/local - - model (text) - - endpoint (text) - - is_default (bool) -- settings - - id (pk, 1) - - audio_prefs (json) // 设备、采样率、VAD - - hud_prefs (json) - - privacy_prefs (json) - -查询与索引 -- turn(conversation_id, start_ms) 索引以便时间序列读取。 -- event(conversation_id, ts) 与 score(conversation_id, ts) 索引。 - -导出与备份 -- 导出单次对话(JSON + 可选音频片段);可批量导出人物档案与曲线。 - - diff --git a/desktop/spec/hud-ux.md b/desktop/spec/hud-ux.md deleted file mode 100644 index 6f6d140..0000000 --- a/desktop/spec/hud-ux.md +++ /dev/null @@ -1,33 +0,0 @@ -HUD 浮窗交互与状态机 - -目标 -- 在不打断用户使用任意聊天应用的情况下,提供实时转写、建议卡与即时反馈动画。 - -布局 -- 位置:屏幕右下角默认;可拖拽并吸附边缘;可记忆上次位置。 -- 结构: - - 顶部:状态条(采集指示、连接状态、静音按钮、展开/收起、设置入口)。 - - 左列:对方转写(系统音频)。 - - 右列:我的转写(麦克风)。 - - 底部:建议卡区域(2-3 张)与“一键复制/插入”按钮。 - - 反馈:好感度进度条 + 流畅的“+N/-N”动画。 - -交互要点 -- 不抢焦点:使用 AlwaysOnTop + 可选 Mouse Through;鼠标靠近即解锁交互。 -- 快捷键:显示/隐藏 HUD,开始/停止监听,复制建议卡(可自定义)。 -- 体积自适应:当无转写/无建议时自动收起为 64px 小条;有活动时展开。 - -状态机(简) -- idle → preparingDevices → listening → suggesting → feedback → listening - - preparingDevices:设备枚举与权限确认;失败进入 error 子状态并给出修复指南。 - - suggesting:接收 LLM 流式返回,逐步渲染卡片。 - - feedback:评分动画 1-2s,不阻塞继续监听。 - -无障碍与国际化 -- 字体放大、对比度增强;中英文切换;屏幕阅读器标签。 - -异常与边界 -- 设备被占用/拔出:顶部状态条显红点并可一键重试。 -- CPU/延迟过高:自动降低分段长度或暂停系统音频,仅保留麦克风。 - - diff --git a/desktop/spec/images/70433c63813d57abeb67ff85363cbc0c.jpg b/desktop/spec/images/70433c63813d57abeb67ff85363cbc0c.jpg deleted file mode 100644 index 1242fcbb4e2d26f97916210941cfcec32705f0a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95173 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 L=Psd}#drS?r}L7w diff --git "a/desktop/spec/images/llm\351\205\215\347\275\256.html" "b/desktop/spec/images/llm\351\205\215\347\275\256.html" deleted file mode 100644 index ee7bbdb..0000000 --- "a/desktop/spec/images/llm\351\205\215\347\275\256.html" +++ /dev/null @@ -1,210 +0,0 @@ - - - - -LiveGalGame - LLM 配置 - - - - - - - - - - - - \ No newline at end of file diff --git "a/desktop/spec/images/\344\270\273\351\241\265.html" "b/desktop/spec/images/\344\270\273\351\241\265.html" deleted file mode 100644 index b30728c..0000000 --- "a/desktop/spec/images/\344\270\273\351\241\265.html" +++ /dev/null @@ -1,230 +0,0 @@ - - - - -LiveGalGame - 总览 - - - - - - - - - - -
- -
-
-
-

欢迎回来!

-

这是您的对话项目快照。

-
-
-
-
-groups -
-
-

攻略对象

-

12

-
-
-
-
-chat_bubble -
-
-

对话

-

89

-
-
-
-
-account_tree -
-
-

分支

-

256

-
-
-
-
-flag -
-
-

故事标记

-

42

-
-
-
-
-
-

最近对话

-
- - -
-
-

“我”与攻略对象的聊天记录摘要。

-
-
-
-
-
-

第1章:命运的相遇

-
-
-
-
-
-

优衣在樱花节上初次遇见健司的开场场景。确立了他们最初的动态关系。

-
-
-

最后编辑:2天前

- -
-
-
-
-
-

咖啡馆的误会

-
-
-
-
-
-

一个关键的冲突场景。健司看到优衣和她的童年朋友明说话,导致了一个主要的情节点。

-
-
-

最后编辑:5天前

- -
-
-
-
-
-

图书馆的告白

-
-
-
-
-

一个感人的独白,优衣在学校图书馆学习到很晚时向玩家表白了她的感情。

-
-
-

最后编辑:1周前

- -
-
-
-add_circle -

创建新对话

-

从头开始一段新对话。

-
-
-
-
-
- \ No newline at end of file diff --git "a/desktop/spec/images/\345\257\271\350\257\235.html" "b/desktop/spec/images/\345\257\271\350\257\235.html" deleted file mode 100644 index 99f98e0..0000000 --- "a/desktop/spec/images/\345\257\271\350\257\235.html" +++ /dev/null @@ -1,349 +0,0 @@ - - - - -LiveGalGame - 对话编辑器 - - - - - - - - - - -
- -
-
-
-
-
-

与 Miyu 的对话*

-
-mood愉快 -local_florist日常 -
-
-
-
- - - -
-
-
-
-
- -
-auto_awesome -

AI分析报告

-
-expand_more -
-
-
-
-

表达能力

-

88

-
-
-star -star -star -star -star -
-

清晰流畅

-
-
-
-

话题选择

-

92

-
-
-star -star -star -star -star_half -
-

非常契合

-
-
-
-
- -
-history_toggle_off -

关键时刻回放

-
-expand_more -
-
-
-
-
-
-

05:12

-

对方:“嗯,好像没什么特别的。”

-
-

AI评价: 回应过于平淡,错失了展开话题的机会。

-
-
-
-
-
-
- -
-psychology -

对方性格分析

-
-expand_more -
-
-

内向、文艺、慢热。喜欢安静的氛围,对艺术和文学话题感兴趣。

-
-
-
- -
-lightbulb -

行动建议

-
-expand_more -
-
-
    -
  • -check_circle -可以尝试的话题:最近读过的书、喜欢的音乐风格 -
  • -
  • -cancel -避开的话题:过于功利的现实问题 -
  • -
-
-
-
-
-
-
-
-

攻略对象

-
-

哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。

-
- -
-
-
-
-
-
-

-
-

听起来真不错!也许我们可以一起去?

-
- -
-
-
-
-
-
-
-
-

攻略对象

-
-

-我当然愿意! 我们在哪里见面好呢? -

-
- -
-
-
-
-
-
-auto_awesome -

AI分析:积极回应!对方接受了你的邀请,表明好感度提升。

-
-
-mood愉快 -favorite心动 -
-
-
-
- -
-
-
-
- -
- \ No newline at end of file diff --git "a/desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" "b/desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" deleted file mode 100644 index d4ba09b..0000000 --- "a/desktop/spec/images/\345\257\271\350\257\235\351\241\265\351\235\242.html" +++ /dev/null @@ -1,251 +0,0 @@ - - - - -LiveGalGame - 对话编辑器 - - - - - - - - - - -
- -
-
-
-
-
-

与 Miyu 的对话*

-
-mood愉快 -local_florist日常 -
-
-
-
- - - -
-
-
-
-
-
-

攻略对象

-
-

哦,真的吗?我刚才还在想,待会儿要不要去散散步。这个季节的樱花应该很美。

-
- -
-
-
-
-
-
-

-
-

听起来真不错!也许我们可以一起去?

-
- -
-
-
-
-
-
-
-
-

攻略对象

-
-

-我当然愿意! 我们在哪里见面好呢? -

-
- -
-
-
-
-
-
-auto_awesome -

AI分析:积极回应!对方接受了你的邀请,表明好感度提升。

-
-
-mood愉快 -favorite心动 -
-
-
-
- -
-
-
- -
- \ No newline at end of file diff --git "a/desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" "b/desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" deleted file mode 100644 index 671259b..0000000 --- "a/desktop/spec/images/\346\202\254\345\201\234\345\212\251\346\211\213.html" +++ /dev/null @@ -1,182 +0,0 @@ - - - - -LiveGalGame AI 助手 - - - - - - - - - - -
-
-
-

对话: Evelyn

-
- - -
-
-
-
-
-

没想到今晚会在这里见到你。

-
-
-
-
-

这个城市充满了惊喜,不是吗?

-
-
-
-
-

也可以这么说。我听说你在调查“猩红集团”的案子。

-
-
-
-
-

只是跟进几条线索,还没什么实质进展。

-
-
-
-
-

AI 推荐:

-
-
-

“你似乎对此了解很多。愿意分享一下吗?”

-
-
积极
-
最佳匹配
-
-
-
-

“这是一条危险的路。我建议你远离。”

-
-
中性
-
创意
-
-
-
-
-
-
-

AI 思考中...

-
-
-
-
- - - - -
-sentiment_very_satisfied -
-
-
-
- - - -
-
-
-
-
-

AI 选项

- -
-
-
-

重要时刻

-
-
-

提及“猩红集团”

-

Evelyn似乎在调查此案。

-
-
-

意外的相遇

-

对话开头的关键转折点。

-
-
-
-
- - -
-
- - -
-
-自动建议触发 - -
-
-紧凑显示模式 - -
-
-
-
-
-
- - - - -
-sentiment_very_dissatisfied -
-
-
-
- \ No newline at end of file diff --git a/desktop/spec/llm-integration.md b/desktop/spec/llm-integration.md deleted file mode 100644 index 50118f3..0000000 --- a/desktop/spec/llm-integration.md +++ /dev/null @@ -1,34 +0,0 @@ -LLM 集成与配置体验 - -用户体验(设置 → 连接测试 → 首次成功) -- 模型选择以结果导向文案呈现: - - 最智能(性能优先) - - 情感理解好(情感/社交优化) - - 隐私更强(本地/自建推理) -- 输入 API Key/Endpoint/模型名;“测试连接”触发一次最小请求并显示: - - 成功/失败、耗时、流式延迟估计;失败提供可执行修复建议(网络/权限/额度)。 -- 支持多配置并可设默认;星标为默认模型。 - -架构与接口 -- Provider 插件化:OpenAI、Anthropic、本地 Oobabooga(text-generation-webui 或 OpenAI-compatible)。 -- 统一接口: - - createClient(config) - - streamCompletion({ system, messages, tools?, temperature?, maxTokens? }) → AsyncIterable - - embeddings?(input) → number[] -- 速率限制与并发队列:防止 HUD 产生的多路请求拥塞;错误可重试。 - -提示工程(简要) -- 系统提示由三部分组成: - 1) 人物档案摘要(昵称、关系、偏好、禁忌)。 - 2) 对话上下文摘要(最近 N 轮关键句)。 - 3) 目标策略(提高好感/婉拒/推进邀约等)。 -- 建议卡模板:标题/一句话/标签/风险提示/效果预测;可 A/B 测试多个模板。 - -本地化与隐私 -- 本地模型:通过 HTTP 兼容接口访问(例如 Oobabooga/openai-compatible);默认关闭遥测。 -- 网络代理:支持自定义代理;建议在下载大模型/资源前执行 `dl1` 启用代理以加速。 - -错误与降级 -- 当云端失败:自动切至备用 Provider;或退化为“建议模板库 + 关键词检索”模式。 - - diff --git a/desktop/spec/prd-desktop.md b/desktop/spec/prd-desktop.md deleted file mode 100644 index 4ff8c4b..0000000 --- a/desktop/spec/prd-desktop.md +++ /dev/null @@ -1,60 +0,0 @@ -桌面版 PRD(基于 Core User Journey) - -目标与定位 -- 目标用户:希望提升与特定对象沟通质量、获得实时指导与复盘的用户(如 Persona“小明”)。 -- 核心定位:在桌面聊天场景中提供“实时辅助 + 即时反馈 + 复盘学习”的完整闭环。 -- 平台:Electron(Windows/macOS),须双平台可打包与运行。 -- 非目标:不提供摄像头相关功能;不内置聊天平台(对现有微信/QQ/Discord 等进行并行辅助)。 - -核心用户旅程(桌面化) -阶段一 准备就绪(Onboarding) -1. 启动与权限 - - 仅请求:麦克风、系统音频(通过屏幕录制/桌面捕获)。逐步解释“为什么需要”,支持稍后再说。 - - Aha 时刻:连接测试成功提示;音频测试听写可见。 -2. 引导式配置 - - 选择“AI 大脑”:用结果导向标签(最智能/情感更好/隐私更强)映射到不同模型与推理路径。 - - 输入 API Key/本地端点后可“测试连接”,即时反馈成功与延迟/速率。 -3. 创建对话存档 - - 建立“人物档案”:昵称、关系标签(如“暧昧对象”)、备注(偏好、禁忌、背景信息)。 - - 备注直接作为系统提示工程的一部分以提升效果。 - -阶段二 实时战场(In-Conversation) -1. HUD 无缝融入 - - 点击“开始对话”后,主窗口最小化/驻留托盘;屏幕角落出现半透明 HUD。 - - HUD 不抢焦点,可鼠标穿透/置顶切换;跟随当前活动应用自动调暗/收起。 -2. 实时转录 - - 左侧:对方(系统音频/通话)转写;右侧:用户(麦克风)转写。 - - 支持 VAD、去噪、分段;最近 30-60 秒可滚动回看;关键词高亮。 -3. AI 建议触发(Magic Moment) - - 基于对方语句意图识别(问候/邀约/抱怨/转场等)和目标(增进好感/转移话题/婉拒)。 - - 生成 2-3 张“建议卡片”:标题+一句话+标签+效果预测(❤️±X)。 - - 选择后进入编辑框,可一键复制/朗读提示(可选 TTS)。 -4. 即时反馈 - - 监听用户发送或口述完成后,HUD 播放好感度动画(从 50→70,绿色“+20”上浮)。 - - HUD 显示轻量解释“为什么有效”,强化正反馈。 - -阶段三 复盘进化(Post-Conversation) -1. 数据沉淀 - - 主窗体“人物卡”展示对话次数、当前好感度、最近一次时间。 -2. 可视化进程 - - 人物详情含“好感度变化曲线”“关键事件时间轴(触发点/建议卡/结果)”。 -3. AI 复盘分析 - - 结构化报告:做得好/可改进/下次建议;可导出或收藏为“提示模板”。 - -成功指标(北极星与 KPI) -- Onboarding 完成率(安装→连接测试→音频测试→创建首个存档)≥ 70% -- 首次 Real-time 建议触发率 ≥ 60%,建议被采用率 ≥ 40% -- 单次对话后留存(次日/7 日)≥ 30%/15% -- 复盘页打开率 ≥ 50%,报告完整阅读率 ≥ 30% - -范围与约束 -- 不采集摄像头;不读取通讯录;仅在本地存储对话转写和评分(可选云同步)。 -- macOS 需屏幕录制权限用于系统音频;Windows 通过 desktopCapturer + WASAPI。 -- 模型可选:云端(OpenAI/Anthropic 等)、本地(如 Oobabooga/LLM 推理服务);需可切换与默认选择。 - -验收标准(MVP) -- Win/mac 可安装运行;首次引导含连接测试与音频测试。 -- HUD 能在微信/Discord 上稳定悬浮,展示双通道转写与建议卡。 -- 好感度动画与结果记录可回看;复盘页可生成基础结构化报告。 - - diff --git a/desktop/spec/privacy-and-permissions.md b/desktop/spec/privacy-and-permissions.md deleted file mode 100644 index 83d5e7e..0000000 --- a/desktop/spec/privacy-and-permissions.md +++ /dev/null @@ -1,30 +0,0 @@ -隐私、权限与安全 - -原则 -- 最小权限:仅在需要时请求;不请求摄像头。 -- 本地优先:默认本地存储;敏感信息加密;云端同步为可选。 -- 透明可控:清晰说明用途;提供一键清除与导出。 - -权限(按平台) -- 麦克风(Win/mac):用于采集用户语音与转写。 -- 屏幕录制(macOS):用于获取系统音频轨(通过屏幕共享音频),不采集视频帧。 -- 全局快捷键(可选):需要监听系统级快捷键时。 - -数据处理 -- 对话转写、事件与评分存储在本地 SQLite。 -- API Key/Token 存 OS Keychain(macOS)或 DPAPI(Windows)。 -- 日志不包含原始对话文本(默认);调试模式下经用户同意后可临时启用。 - -联网与模型 -- 云端模型:仅发送必要上下文(摘要+最近窗口);提供“离线/本地模式”。 -- 代理与加速:支持用户自定义代理;下载大文件前给出提示,可使用多进程分片。 - -用户控制 -- 一键暂停监听(HUD/托盘);暂停期间不捕获音频。 -- 数据管理:删除某次对话/某人全部历史;导出 JSON/CSV 报告。 -- 权限管理:设置页展示已授予权限与撤销指引。 - -合规(参考) -- 遵循 GDPR/CCPA 的最小化与可删除权;遵循平台隐私政策与签名要求。 - - diff --git a/desktop/spec/tech-architecture.md b/desktop/spec/tech-architecture.md deleted file mode 100644 index fdf3ddc..0000000 --- a/desktop/spec/tech-architecture.md +++ /dev/null @@ -1,57 +0,0 @@ -技术架构(Electron Win/mac) - -总体分层 -- 应用壳(Electron) - - Main 进程:窗口与菜单管理、托盘、协议拦截、系统权限与设备枚举、快捷键注册、持久化层、更新与崩溃上报、后台任务调度。 - - Renderer 渲染器:主窗体(仪表盘/存档/复盘)、HUD 浮窗、设置页、模型配置页。 - - Preload:安全桥接(contextIsolation),暴露受控 API(音频设备列表、录制控制、模型推理调用、DB 访问)。 -- 实时能力 - - 音频采集层:麦克风(getUserMedia)与系统音频(desktopCapturer/屏幕共享音轨/虚拟声卡),统一为标准 MediaStream。 - - 转写 ASR:可插拔(本地 Whisper/WebRTC-ASR、远端流式 ASR)。通过 WebWorker/Node Worker Threads 进行推理或数据编解码。 - - 语义分析与建议生成:LLM 推理(流式),意图分类、策略模板填充、效果预测打分。 -- 业务域 - - 人物与对话:存档、会话片段、事件(建议卡触发/采纳/得分)。 - - 评分与可视化:好感度曲线、事件时间轴。 -- 存储 - - SQLite(better-sqlite3)作为本地嵌入式数据库;附件(音频片段、报表导出)走应用数据目录。 - - 可选云同步:基于用户账户登录的增量上传(非 MVP)。 -- UI 技术 - - 前端:React + Vite(或 Next.js in SPA 模式),Tailwind/UNO CSS;可复用移动端设计语言但为桌面优化尺寸与布局。 - -关键模块与边界 -- WindowManager(Main) - - 创建/销毁主窗体与 HUD;控制置顶、鼠标穿透、圆角与阴影。 - - 托盘菜单:开始/停止对话、快速切换人物、静音/监听状态。 -- AudioController(Renderer + Preload 桥接) - - 枚举设备;启动/停止录制;回调分发(VAD 边界、RMS 音量、丢包)。 - - Windows:desktopCapturer 选择“全部屏幕 + 系统音频”;macOS:要求屏幕录制权限后获取系统音轨。 -- AsrService - - 输入:双通道音频帧(mic/system);输出:带时间戳的转写段落。 - - 可配置:本地 vs 远端;采样率、分段策略、语言。 -- NlpService / LlmService - - LLM 供应商插件:OpenAI、Anthropic、本地 Oobabooga(HTTP 推理接口)。 - - 统一流式接口(SSE/WebSocket/HTTP chunked)与重试/超时/速率限制。 -- SuggestionEngine - - 触发条件:意图分类、关键词、对话上下文变化。 - - 输出卡片:标题、文案、标签、效果预测分值、追问建议。 -- FeedbackEngine - - 评分规则:基于用户采纳与对话后续反馈;为 HUD 播放动画提供数值与文案。 -- DataLayer(SQLite) - - 表:person、conversation、turn、event、score、model_profile、settings(详见 data-model.md)。 - - 统一事务与迁移;预编译语句;导入/导出。 - -安全与隔离 -- 启用 contextIsolation、关闭 nodeIntegration;仅通过 preload 暴露白名单 API。 -- CSP 与内容来源白名单;敏感配置(API Key)加密存储(OS Keychain/DPAPI)。 -- 自动更新与签名校验(electron-updater + code sign)。 - -性能与稳定性 -- 音频与 ASR 在独立线程/进程执行,避免阻塞 UI。 -- 流水线背压:ASR 输出分段驱动 LLM;超时与丢包处理。 -- 日志分级:主进程/渲染器/服务各自记录并汇聚(可选 Sentry)。 - -可扩展性 -- Provider 插件:新增 LLM/ASR 仅需实现统一接口。 -- 建议卡模板:以 JSON/Prompt 模板化,可热更新。 - - diff --git a/desktop/spec/test-plan.md b/desktop/spec/test-plan.md deleted file mode 100644 index 53d78d3..0000000 --- a/desktop/spec/test-plan.md +++ /dev/null @@ -1,38 +0,0 @@ -测试计划与验收标准 - -环境矩阵 -- Windows 10 19045、Windows 11;x64(arm64 可选) -- macOS 12/13/14;Intel/Apple Silicon - -功能用例 -1) 安装与首次启动 - - 成功安装并启动,无报错;显示 Onboarding 向导。 -2) 权限与设备 - - Win:请求麦克风权限;系统音频可捕获。 - - mac:首次捕获系统音频触发屏幕录制权限;授权后成功。 - - 设备枚举、切换麦克风/输出设备成功。 -3) 连接测试 - - OpenAI/Anthropic/本地端点分别测试成功;错误信息可读且包含修复建议。 -4) 音频测试 - - 说话被准确转写;播放音乐/通话可见系统音轨波形与转写(如能)。 -5) 创建人物与对话 - - 新建人物、填写备注后保存;开始对话进入 HUD。 -6) HUD 实时体验 - - 双通道转写正常;建议卡在 2-3 秒内出现;可复制/插入;快捷键生效。 - - 反馈动画连贯;数值记录入库。 -7) 复盘页 - - 曲线正确;事件时间轴显示建议卡触发/采纳;生成结构化报告。 -8) 数据管理 - - 删除单次对话;导出/导入 JSON;API Key 加密保存。 -9) 稳定性 - - 长时间会话(≥30 分钟)CPU/内存可控;丢包/网络抖动下自动恢复。 - -性能门槛(MVP) -- HUD 展开/收起 ≤ 120ms -- 建议卡首字节 ≤ 2.0s(云端),≤ 3.0s(本地模型) -- 进程常驻内存:空闲 < 300MB;会话中 < 800MB(视模型与本地 ASR 而定) - -验收 -- 通过所有功能用例;未解决的高优缺陷为 0;签名/公证通过;打包物可在两平台安装与运行。 - - diff --git a/desktop/spec/ui-design-01-chat-window.md b/desktop/spec/ui-design-01-chat-window.md deleted file mode 100644 index 0abf7ca..0000000 --- a/desktop/spec/ui-design-01-chat-window.md +++ /dev/null @@ -1,460 +0,0 @@ -# UI 设计文档:对话窗口(聊天界面) - -> **原型图参考**:Image 1 - 与 Evelyn 的对话窗口 -> **对应功能**:实时对话 HUD,AI 建议生成与选择 - ---- - -## 1. 整体布局 - -### 1.1 窗口属性 -- **窗口类型**:半透明浮动窗口(Always-on-Top) -- **默认尺寸**:宽 440px,高度自适应内容(最小 600px,最大 800px) -- **背景**:深灰色磨砂半透明 `rgba(68, 68, 68, 0.95)`,带 `backdrop-filter: blur(20px)` -- **圆角**:16px -- **阴影**:`0 8px 32px rgba(0, 0, 0, 0.4)` -- **可拖拽**:标题栏区域可拖动整个窗口 -- **位置记忆**:关闭后记住上次位置,下次启动时恢复 - -### 1.2 布局结构(从上到下) -``` -┌─────────────────────────────────────────┐ -│ [标题栏] 对话: Evelyn [-][□][×] │ ← 高度 48px -├─────────────────────────────────────────┤ -│ │ -│ [对话气泡区域] │ ← 可滚动,flex-grow -│ └─ 左侧气泡(对方) │ -│ └─ 右侧气泡(自己) │ -│ └─ 标星标记(重要内容) │ -│ │ -├─────────────────────────────────────────┤ -│ [AI 推荐区域] │ ← 高度自适应(0-240px) -│ "你似乎对此了解很多..." │ -│ └─ [拒绝 按钮] [暧昧回应 按钮] │ -│ "这是一条危险的路..." │ -│ └─ [中性 按钮] [创意 按钮] │ -│ │ -├─────────────────────────────────────────┤ -│ AI 思考中... │ ← 加载状态,高 32px -├─────────────────────────────────────────┤ -│ [底部操作栏] │ ← 高度 64px -│ [😊] [⏸] [✨] [⚙] │ -└─────────────────────────────────────────┘ -``` - ---- - -## 2. 标题栏(Title Bar) - -### 2.1 视觉规范 -- **高度**:48px -- **背景**:渐变 `linear-gradient(135deg, #D91B5C 0%, #C2185B 100%)`(品牌粉红色) -- **文字**:白色,字体 16px,粗体(font-weight: 600) -- **格式**:`对话: {角色名称}`(左对齐,左边距 16px) -- **右侧按钮**: - - 最小化 `−`、最大化 `□`、关闭 `×` - - 尺寸:每个 32×32px,hover 时背景变为 `rgba(255,255,255,0.2)` - - 间距:按钮间距 4px,右边距 8px - -### 2.2 交互行为 -- **拖拽**:鼠标在标题栏任意位置按下可拖动窗口 -- **双击**:双击标题栏切换最大化/还原 -- **最小化**:窗口缩小到托盘/任务栏,对话继续监听 -- **关闭**:弹出确认对话框 "是否保存并退出对话?",有 "保存"/"放弃"/"取消" 三个选项 - ---- - -## 3. 对话气泡区域 - -### 3.1 容器属性 -- **背景**:透明 -- **内边距**:上下 16px,左右 16px -- **滚动**:`overflow-y: auto`,自定义滚动条样式(宽 6px,深灰色轨道,浅灰色滑块) -- **排列**:垂直 Flexbox,从上到下 - -### 3.2 对方消息气泡(左侧) -``` -┌────────────────────────────────┐ -│ 没想到今晚会在这里见到你。 │ -├────────────────────────────────┤ -│ 这个城市充满了惊喜,不是吗? │ -└────────────────────────────────┘ -``` - -**样式规范**: -- **对齐**:左对齐(margin-right: auto) -- **最大宽度**:70% -- **背景色**:`rgba(100, 116, 139, 0.3)`(浅灰蓝半透明) -- **文字颜色**:`#E2E8F0`(浅灰白) -- **字体**:14px,行高 1.6 -- **内边距**:12px 16px -- **圆角**: - - 左上 4px,右上 16px - - 左下 16px,右下 16px - - (模拟对话气泡的"尾巴"在左上) -- **间距**:相邻气泡间距 8px -- **连续消息**:如果连续多条来自同一方,第一条用完整圆角,中间条用统一圆角 12px,最后一条用"尾巴"圆角 - -### 3.3 自己的消息气泡(右侧) -``` - ┌────────────────────────────┐ - │ 听起来真不错!也可以这么说,│ - │ 我听说你在调查"猩红集团"的 │ - │ 案子。 ⭐│ - └────────────────────────────┘ -``` - -**样式规范**: -- **对齐**:右对齐(margin-left: auto) -- **最大宽度**:70% -- **背景色**:`rgba(217, 27, 92, 0.2)`(品牌粉色半透明) -- **文字颜色**:`#FFF`(纯白) -- **字体**:14px,行高 1.6 -- **内边距**:12px 16px -- **圆角**: - - 左上 16px,右上 4px - - 左下 16px,右下 16px - - ("尾巴"在右上) -- **星标标记**: - - 位置:右下角,绝对定位 `bottom: 4px, right: 4px` - - 图标:⭐(红色五角星,尺寸 16px) - - 显示条件:该消息被 AI 标记为"攻略关键点"时显示 - - Hover 提示:显示 tooltip "关键进展:好感度 +20" - -### 3.4 消息元数据 -- **时间戳**:每条消息气泡下方居中显示,格式 `14:32` -- **样式**:10px,颜色 `rgba(255,255,255,0.4)`,上边距 4px -- **合并规则**:1 分钟内的连续消息不重复显示时间 - ---- - -## 4. AI 推荐区域 - -### 4.1 整体布局 -- **触发时机**:检测到对方说话结束后 2 秒,从底部滑入动画(300ms ease-out) -- **背景**:`rgba(30, 30, 30, 0.95)`,顶部有 1px 分隔线 `rgba(255,255,255,0.1)` -- **内边距**:16px -- **最大高度**:240px,超出部分可滚动 - -### 4.2 推荐卡片结构 -每个推荐选项为一个卡片,结构如下: - -``` -┌────────────────────────────────────────┐ -│ "你似乎对此了解很多。愿意分享一下吗?" │ ← 建议文本 -│ │ -│ [拒绝] [暧昧回应] │ ← 标签按钮 -└────────────────────────────────────────┘ -``` - -**卡片样式**: -- **背景**:`rgba(51, 65, 85, 0.6)`(深蓝灰) -- **圆角**:12px -- **内边距**:12px 16px -- **边框**:1px solid transparent(默认),hover 时变为 `rgba(217, 27, 92, 0.5)` -- **间距**:卡片之间 12px -- **过渡**:所有 hover 效果 200ms ease - -### 4.3 建议文本 -- **字体**:15px,行高 1.5 -- **颜色**:`#FFF` -- **最大行数**:3 行,超出显示省略号 -- **引号**:使用中文双引号 `" "` - -### 4.4 标签按钮 -- **布局**:Flexbox 横向排列,`justify-content: space-between` -- **单个按钮**: - - **尺寸**:高 32px,宽度自适应(最小 80px),水平内边距 16px - - **背景色**:根据标签类型变化: - - 拒绝:`rgba(239, 68, 68, 0.2)` + 红色边框 - - 暧昧回应:`rgba(236, 72, 153, 0.3)` + 粉色边框 - - 中性:`rgba(148, 163, 184, 0.2)` + 灰色边框 - - 创意:`rgba(59, 130, 246, 0.3)` + 蓝色边框 - - **文字**:12px,粗体 500,颜色对应标签色(高亮版本) - - **圆角**:6px - - **Hover**:背景不透明度增加到 0.5,指针变为 pointer - - **点击效果**:按钮缩放 0.95,100ms 后恢复 - -### 4.5 效果预测徽章(可选) -- **位置**:在标签按钮右侧,间距 8px -- **格式**:`❤️ +20` 或 `❤️ -5` -- **样式**: - - 字体 11px - - 正值:绿色 `#10B981` - - 负值:橙色 `#F59E0B` - - 背景:`rgba(0,0,0,0.3)`,圆角 4px,内边距 2px 6px - -### 4.6 关闭按钮 -- **位置**:推荐区域右上角 -- **图标**:`×`,尺寸 20px -- **样式**:颜色 `rgba(255,255,255,0.5)`,hover 时 `#FFF` -- **行为**:点击后推荐区域向下滑出消失(300ms),用户可以手动输入 - ---- - -## 5. AI 状态指示器 - -### 5.1 思考状态 -``` -AI 思考中... -``` -- **显示时机**:对方消息结束后,AI 生成建议的等待期间 -- **位置**:推荐区域上方,独立一行 -- **背景**:`rgba(59, 130, 246, 0.1)`(淡蓝色) -- **高度**:32px -- **文字**: - - 内容:`AI 思考中...`(带动画三个点循环显示) - - 字体:13px,斜体 - - 颜色:`#60A5FA`(淡蓝色) - - 居中对齐 -- **动画**:脉冲效果(opacity 0.6 ↔ 1.0,1.5s 循环) - -### 5.2 错误状态 -- **显示时机**:AI 调用失败或超时(5 秒) -- **样式**: - - 背景变为 `rgba(239, 68, 68, 0.15)`(淡红色) - - 文字:`AI 暂时无法响应,请检查网络或稍后重试` - - 颜色:`#F87171`(红色) - - 右侧显示"重试"按钮(小型按钮,60px 宽) - ---- - -## 6. 底部操作栏 - -### 6.1 布局 -- **高度**:64px -- **背景**:`rgba(30, 30, 30, 0.95)`,顶部 1px 分隔线 -- **内边距**:16px -- **布局**:Flexbox 横向居中排列,`gap: 24px` - -### 6.2 按钮定义 -``` -[😊] [⏸] [✨] [⚙] -情绪 暂停监听 AI助手 设置 -``` - -#### 按钮通用样式 -- **尺寸**:48×48px 圆形按钮 -- **背景**:`rgba(100, 116, 139, 0.3)` -- **图标**:24×24px,颜色 `#E2E8F0` -- **Hover**:背景变为 `rgba(217, 27, 92, 0.5)`,图标放大 1.1 倍(200ms 过渡) -- **Active**:背景变为品牌粉色 `#D91B5C`,图标为白色 - -#### 各按钮功能 -1. **情绪按钮(😊)**: - - 点击展开情绪表情选择器(emoji picker) - - 用途:手动标记当前对话的情绪氛围 - - 选择器:弹出在按钮上方,5×4 网格,包含常用表情 - -2. **暂停/播放按钮(⏸/▶)**: - - 切换监听状态 - - 暂停时:图标变为 `▶`,背景变为橙色 `#F59E0B` - - 作用:临时停止音频捕获和 AI 分析 - -3. **AI 助手按钮(✨)**: - - 点击手动触发 AI 建议生成 - - 作用:当 AI 未自动响应时,用户可以主动请求建议 - - 状态:生成中时旋转动画(360° 无限循环) - -4. **设置按钮(⚙)**: - - 点击打开快速设置菜单(下拉弹窗) - - 包含: - - 切换对话对象 - - 调整透明度(滑块 50%-100%) - - 快捷键设置 - - 返回主窗口 - ---- - -## 7. 交互动画与过渡 - -### 7.1 消息发送动画 -1. 用户参考建议后说话 -2. 新气泡从右侧 150% 位置滑入(300ms ease-out) -3. 同时透明度 0 → 1 -4. 如果是关键进展,星标延迟 500ms 后闪烁出现(scale 0.5 → 1.2 → 1.0) - -### 7.2 好感度变化动画 -- **触发**:AI 判定产生好感度变化时 -- **动画**: - 1. 在相关消息气泡上方飘出数字 `+20` - 2. 颜色为绿色 `#10B981`,字体 18px 粗体 - 3. 向上移动 40px,同时透明度 1 → 0(1000ms) - 4. 移动曲线使用 `cubic-bezier(0.25, 0.46, 0.45, 0.94)` - -### 7.3 推荐区域滑入/滑出 -- **滑入**:从 `translateY(100%)` 到 `translateY(0)`,300ms ease-out -- **滑出**:从 `translateY(0)` 到 `translateY(100%)`,300ms ease-in -- **高度过渡**:max-height 从 0 到实际高度(300ms) - ---- - -## 8. 响应式与状态管理 - -### 8.1 窗口尺寸变化 -- **最小宽度**:360px -- **最小高度**:480px -- **缩放规则**: - - 气泡最大宽度按比例调整(始终为容器宽度的 70%) - - 推荐区域在宽度 < 400px 时,标签按钮堆叠为单列 - -### 8.2 焦点状态 -- **窗口失焦**:整体透明度降低到 0.8 -- **窗口重新获得焦点**:透明度恢复到 1.0(200ms 过渡) - -### 8.3 深色/浅色模式(预留) -当前仅支持深色模式,未来可扩展浅色主题变量: -```css ---bg-primary: rgba(68, 68, 68, 0.95); ---text-primary: #E2E8F0; ---accent-color: #D91B5C; -``` - ---- - -## 9. 无障碍(Accessibility) - -### 9.1 键盘导航 -- **Tab 键**:按钮间循环聚焦 -- **Enter/Space**:激活聚焦的按钮 -- **Esc**:关闭推荐区域或设置菜单 -- **Ctrl+W**:关闭窗口(弹出确认) - -### 9.2 屏幕阅读器 -- 所有按钮添加 `aria-label` 属性 -- 消息气泡添加 `role="log"` 和 `aria-live="polite"` -- 推荐卡片添加 `role="option"` 和 `aria-describedby` - -### 9.3 对比度 -- 所有文字与背景对比度 ≥ 4.5:1(符合 WCAG AA 标准) -- 交互元素对比度 ≥ 3:1 - ---- - -## 10. 技术实现提示 - -### 10.1 React 组件结构建议 -```jsx - - - - {messages.map(msg => ( - msg.role === 'user' - ? - : - ))} - - {aiState === 'thinking' && } - {suggestions.length > 0 && ( - - )} - - -``` - -### 10.2 样式方案 -- 使用 **Tailwind CSS** + 自定义主题变量 -- 或使用 **Styled Components** 实现动态主题切换 -- 动画使用 **Framer Motion** 库简化实现 - -### 10.3 性能优化 -- 消息列表使用虚拟滚动(react-window),支持 1000+ 条消息 -- 推荐卡片懒加载,仅渲染可见区域 -- 防抖用户输入事件(300ms) - ---- - -## 11. 设计资源清单 - -### 11.1 图标资源 -- 推荐使用 **Lucide Icons** 或 **Heroicons** -- 需要的图标: - - Minimize, Maximize, Close - - Smile (Emoji), Pause, Play, Sparkles, Settings - - Star (filled/outline) - - X (close small) - -### 11.2 字体 -- **主字体**:思源黑体(Noto Sans SC)或 苹方(PingFang SC) -- **等宽字体**(代码/时间戳):JetBrains Mono - -### 11.3 颜色变量表 -```css -:root { - /* 品牌色 */ - --brand-primary: #D91B5C; - --brand-gradient: linear-gradient(135deg, #D91B5C 0%, #C2185B 100%); - - /* 背景 */ - --bg-window: rgba(68, 68, 68, 0.95); - --bg-panel: rgba(30, 30, 30, 0.95); - --bg-card: rgba(51, 65, 85, 0.6); - - /* 气泡 */ - --bubble-partner: rgba(100, 116, 139, 0.3); - --bubble-user: rgba(217, 27, 92, 0.2); - - /* 文字 */ - --text-primary: #FFFFFF; - --text-secondary: #E2E8F0; - --text-muted: rgba(255, 255, 255, 0.4); - - /* 标签 */ - --tag-reject: rgba(239, 68, 68, 0.2); - --tag-flirt: rgba(236, 72, 153, 0.3); - --tag-neutral: rgba(148, 163, 184, 0.2); - --tag-creative: rgba(59, 130, 246, 0.3); - - /* 状态 */ - --status-success: #10B981; - --status-warning: #F59E0B; - --status-error: #F87171; - --status-info: #60A5FA; -} -``` - ---- - -## 12. 验收标准 - -### 12.1 视觉还原度 -- [ ] 窗口圆角、阴影与原型一致 -- [ ] 品牌色渐变在标题栏正确显示 -- [ ] 消息气泡"尾巴"圆角正确(左/右差异) -- [ ] 星标图标仅在关键消息显示 -- [ ] 推荐卡片标签颜色与原型匹配 - -### 12.2 交互流畅性 -- [ ] 窗口拖拽无卡顿(60fps) -- [ ] 消息滑入动画自然(300ms 以内) -- [ ] 好感度数字飘出动画清晰可见 -- [ ] 推荐区域滑入/滑出无闪烁 -- [ ] 按钮 hover/active 状态响应 < 100ms - -### 12.3 功能完整性 -- [ ] 消息自动滚动到最新一条 -- [ ] 暂停按钮正确切换音频监听状态 -- [ ] 点击推荐后自动填充到对话 -- [ ] 关闭窗口弹出保存确认 -- [ ] 设置菜单正确打开并响应操作 - -### 12.4 跨平台一致性 -- [ ] Windows 和 macOS 窗口控制按钮位置符合平台规范 -- [ ] 字体渲染在两平台清晰 -- [ ] 透明效果在两平台正常(Windows 使用 Acrylic,macOS 使用 Vibrancy) - ---- - -**文档版本**:v1.0 -**最后更新**:2025-11-12 -**维护者**:产品设计团队 - diff --git a/desktop/spec/ui-design-02-llm-config.md b/desktop/spec/ui-design-02-llm-config.md deleted file mode 100644 index a91aff5..0000000 --- a/desktop/spec/ui-design-02-llm-config.md +++ /dev/null @@ -1,577 +0,0 @@ -# UI 设计文档:LLM 配置页面(模型选择与连接) - -> **原型图参考**:Image 2 - LLM 配置管理界面 -> **对应功能**:AI 模型选择、API Key 输入、连接测试、模型启用/禁用 - ---- - -## 1. 整体布局 - -### 1.1 页面属性 -- **页面类型**:主窗口内容区域(非浮窗) -- **尺寸**:全屏适配,宽度 1200px~,高度 800px~ -- **背景**:浅白色 `#F5F7FA`(桌面应用通常背景较亮) -- **滚动**:页面内容超高时支持垂直滚动 - -### 1.2 结构布局(从上到下) -``` -┌──────────────────────────────────────────────────────┐ -│ [侧边栏导航] [主内容区] │ -│ │ -│ ◆ 总览 LLM 配置 │ -│ ◆ 攻略对象 管理你的 AI 模型配置,添加新模型并设置默认值。 -│ ◆ 对话编辑器 -│ ◆ LLM配置(✓) ┌──────────────────────────────┐ -│ ◆ 设置 │ + 添加新模型 [组件预留] │ -│ └──────────────────────────────┘ -│ -│ ┌──────────────────────────────┐ -│ [帮助] │ GPT-4o (OpenAI) │ -│ [报告问题] │ ● 已激活 │ -│ [登出] │ ┌──────────────────────────┐ │ -│ │ │ gpt-4o │ │ -│ │ │ OpenAI 官方最强模型 │ │ -│ │ │ [编辑] [删除] │ │ -│ │ └──────────────────────────┘ │ -│ │ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ -│ └──────────────────────────────┘ -│ -│ ┌──────────────────────────────┐ -│ │ Claude 3 Opus (Anthropic) │ -│ │ ◯ 未激活 │ -│ │ ┌──────────────────────────┐ │ -│ │ │ claude-3-opus-... │ │ -│ │ │ 情感理解更好的模型 │ │ -│ │ │ [编辑] [连接测试] │ │ -│ │ └──────────────────────────┘ │ -│ │ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ -│ └──────────────────────────────┘ -│ -│ ┌──────────────────────────────┐ -│ │ 本地 Llama 3 (Oobabooga) │ -│ │ ⚠ 连接错误 │ -│ │ ┌──────────────────────────┐ │ -│ │ │ 本地 Oobabooga │ │ -│ │ │ 隐私友好的本地模型 │ │ -│ │ │ [编辑] [重新连接] │ │ -│ │ └──────────────────────────┘ │ -│ │ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ -│ └──────────────────────────────┘ -│ -│ [+ 添加你的第一个 AI 模型] -│ 从头开始一段新对话。连接到 OpenAI、 -│ Anthropic 或本地实例。 -│ -└──────────────────────────────────────────────────────┘ -``` - ---- - -## 2. 侧边栏导航 - -### 2.1 容器属性 -- **宽度**:280px(固定) -- **背景**:品牌粉红色渐变 `linear-gradient(135deg, #D91B5C 0%, #C2185B 100%)` -- **高度**:100vh -- **内边距**:20px 0 -- **位置**:绝对定位或 Flexbox 容器左侧 -- **滚动**:如果导航项超过高度,启用滚动 - -### 2.2 导航项结构 -``` -◆ 总览 -◆ 攻略对象 -◆ 对话编辑器 -◆ LLM配置 ✓ -◆ 设置 -``` - -**每个导航项样式**: -- **文字颜色**: - - 默认:`rgba(255, 255, 255, 0.7)` - - Hover:`#FFF` - - Active(当前页):`#FFF` -- **字体**:15px,行高 2.0(项目间距 30px) -- **左内边距**:24px(缩进) -- **背景**: - - 默认:透明 - - Hover:`rgba(255, 255, 255, 0.1)` - - Active:`rgba(255, 255, 255, 0.2)`,左边有 3px 白色竖线指示器 -- **图标**:每项前有小菱形 `◆`(24px 从左边距),颜色与文字同步 - -### 2.3 底部功能区(stickied 到底部) -``` -[帮助图标] 帮助 -[报告图标] 报告问题 -[登出图标] 登出 -``` - -- **位置**:绝对定位 `bottom: 20px, left: 0, right: 0` -- **样式**: - - 背景:`rgba(255, 255, 255, 0.1)` - - 边框顶部:1px solid `rgba(255, 255, 255, 0.2)` - - 内边距:16px 24px - - 每项高 40px,间距 8px -- **文字**:13px,颜色 `rgba(255, 255, 255, 0.7)` -- **图标**:左侧 16×16px,右侧箭头 (→) -- **Hover**:背景变为 `rgba(255, 255, 255, 0.15)`,文字变白 - ---- - -## 3. 顶部标题栏(主内容区上方) - -### 3.1 标题栏布局 -``` -LLM 配置 [+ 添加新模型] -管理你的 AI 模型配置,添加新模型并设置默认值。 -``` - -- **高度**:80px -- **背景**:白色 `#FFFFFF` -- **边框**:底部 1px solid `#E5E7EB` -- **内边距**:20px 40px -- **布局**:Flexbox 列方向 - -### 3.2 标题 -- **文字**:`LLM 配置` -- **字体**:28px,粗体 700,颜色 `#1F2937` -- **下边距**:8px - -### 3.3 副标题 + 操作按钮 -- **布局**:Flex row,`justify-content: space-between` -- **副标题**: - - 文字:`管理你的 AI 模型配置,添加新模型并设置默认值。` - - 字体:14px,颜色 `#6B7280` -- **按钮**:`+ 添加新模型` - - 背景:品牌粉红 `#D91B5C` - - 文字:白色,14px 粗体 - - 内边距:10px 20px - - 圆角:8px - - Hover:背景变深 `#C2185B` - - 阴影:`0 2px 4px rgba(0,0,0,0.1)` - ---- - -## 4. 模型卡片(核心内容) - -### 4.1 卡片容器 -``` -┌─────────────────────────────────────────────────┐ -│ GPT-4o (OpenAI) │ -│ ● 已激活 │ -│ ┌────────────────────────────────────────────┐ │ -│ │ gpt-4o │ │ -│ │ OpenAI 官方最强模型。准确率 98%+,适合 │ │ -│ │ 多轮对话与情感分析。 │ │ -│ │ [编辑] [连接测试] │ │ -│ └────────────────────────────────────────────┘ │ -│ ⚙ 设为默认 ☆ 收藏 🗑 删除 │ -└─────────────────────────────────────────────────┘ -``` - -**卡片样式**: -- **背景**:白色 `#FFFFFF` -- **圆角**:12px -- **边框**:1px solid `#E5E7EB` -- **阴影**:`0 1px 3px rgba(0, 0, 0, 0.1)`,hover 时升高到 `0 4px 12px rgba(0, 0, 0, 0.15)` -- **内边距**:20px -- **间距**:卡片之间 16px -- **过渡**:所有变化 200ms ease -- **最大宽度**:800px(居中对齐) - -### 4.2 卡片头部(标题 + 状态) -- **布局**:Flex row,`justify-content: space-between, align-items: center` -- **左侧**: - - **标题**:`GPT-4o (OpenAI)`,字体 18px 粗体 600,颜色 `#1F2937` -- **右侧**: - - **状态指示器**(3 种): - 1. 已激活:`● 已激活`,颜色绿色 `#10B981` - 2. 未激活:`◯ 未激活`,颜色灰色 `#9CA3AF` - 3. 连接错误:`⚠ 连接错误`,颜色红色 `#EF4444` - - 字体:13px,粗体 500 - - 右边距:0px - -### 4.3 卡片主体(模型信息) -- **背景**:`#F9FAFB`(极浅灰) -- **圆角**:8px -- **内边距**:12px 16px -- **字体**:13px,行高 1.6 -- **内容**: - - 第一行:模型 ID(`gpt-4o`),字体粗体 500,颜色 `#1F2937` - - 第二行:模型描述(`OpenAI 官方最强模型...`),颜色 `#6B7280` - - 第三行:按钮组 `[编辑] [连接测试]` 或根据状态显示 - -### 4.4 卡片底部(操作栏) -- **布局**:Flex row,`justify-content: flex-start, gap: 16px` -- **操作按钮**(4 个,按序): - 1. **⚙ 设为默认** - - 尺寸:高 32px,宽 120px - - 背景:透明,边框 1px `#D1D5DB` - - 文字:13px,颜色 `#374151` - - Hover:背景 `#F3F4F6`,边框 `#9CA3AF` - - 2. **☆ 收藏**(未收藏状态)或 **★ 已收藏**(已收藏状态) - - 尺寸:高 32px,宽 100px - - 背景:透明,边框 1px `#D1D5DB` - - 文字:13px - - 未收藏:颜色 `#6B7280` - - 已收藏:颜色 `#FCD34D`(金黄色),边框 `#FCD34D` - - 3. **🗑 删除** - - 尺寸:高 32px,宽 100px - - 背景:透明,边框 1px `#FCA5A5`(淡红) - - 文字:13px,颜色 `#EF4444`(红) - - Hover:背景 `#FEE2E2` - - 4. **编辑按钮**(仅在卡片主体中显示) - - 位置:模型信息底部,与"连接测试"并排 - - 尺寸:高 28px,宽 60px - - 背景:品牌粉色 `#D91B5C` - - 文字:12px,颜色白色 - - 圆角:4px - - Hover:背景 `#C2185B` - -### 4.5 条件渲染规则 -根据模型连接状态,卡片内容和按钮不同: - -| 状态 | 卡片右侧指示器 | 背景色调整 | 可用按钮 | -|------|---|---|---| -| 已激活 | ● 绿色 | 无调整 | 设为默认、收藏、编辑、删除、连接测试 | -| 未激活 | ◯ 灰色 | 轻度灰化 opacity 0.6 | 设为默认、收藏、编辑、删除、连接测试 | -| 连接错误 | ⚠ 红色 | 轻度红化 opacity 0.8 | 设为默认、收藏、编辑、删除、重新连接 | - ---- - -## 5. 模态对话:添加新模型 - -### 5.1 对话框属性 -触发方式:点击"+ 添加新模型"按钮或空卡片上的链接 - -- **宽度**:600px -- **背景**:白色 `#FFFFFF` -- **圆角**:12px -- **阴影**:`0 10px 40px rgba(0, 0, 0, 0.2)` -- **z-index**:确保在所有内容上方 - -### 5.2 对话框结构 -``` -┌────────────────────────────────────────┐ -│ 添加 AI 模型 [×] │ -├────────────────────────────────────────┤ -│ 选择模型供应商: │ -│ ┌──────────────────────────────────┐ │ -│ │ OpenAI (GPT-4o, GPT-3.5...) ▼ │ │ ← Dropdown -│ └──────────────────────────────────┘ │ -│ │ -│ API Key / 认证信息: │ -│ ┌──────────────────────────────────┐ │ -│ │ sk-proj-xxxxx... (粘贴你的 key) │ │ ← Input -│ └──────────────────────────────────┘ │ -│ │ -│ [测试连接] [保存] [取消] │ -└────────────────────────────────────────┘ -``` - -### 5.3 字段定义 - -#### 供应商选择器 -- **标签**:`选择模型供应商:`,14px 粗体 600 -- **下拉框**: - - 选项: - - OpenAI (GPT-4o, GPT-3.5...) - - Anthropic (Claude 3 Opus, Sonnet...) - - 本地 (Ollama, Oobabooga, Vllm...) - - 自定义 API (兼容 OpenAI 格式) - - 高度:40px - - 背景:`#F9FAFB`,边框 1px `#D1D5DB` - - 文字:14px,颜色 `#1F2937` - - 圆角:6px - - 内边距:10px 12px - - Hover:边框 `#9CA3AF` - -#### API Key 输入框 -- **标签**:`API Key / 认证信息:`,14px 粗体 600,下边距 8px -- **输入框**: - - 高度:40px - - 背景:`#F9FAFB`,边框 1px `#D1D5DB` - - 文字:13px,颜色 `#374151` - - 圆角:6px - - 内边距:10px 12px - - Placeholder:`粘贴你的 API Key(仅本地存储,不上传)`,颜色 `#9CA3AF` - - Focus:边框变为品牌粉色 `#D91B5C`,阴影 `0 0 0 3px rgba(217, 27, 92, 0.1)` - - 密码隐藏:启用密码显示切换按钮(👁 符号) - -#### 高级选项(可折叠) -- **折叠标题**:`⚙ 高级选项`,13px,颜色 `#6B7280` -- **展开内容**(不详细列出): - - 模型名称自定义(override 模型 ID) - - 温度系数(Temperature)滑块 0.0 ~ 2.0 - - 最大 Token 数量 - - 代理设置(可选) - -### 5.4 按钮栏 -``` -[测试连接] [保存] [取消] -``` - -- **布局**:Flex row,`justify-content: space-between, align-items: center` -- **左侧**: - - **[测试连接]** 按钮 - - 背景:蓝色 `#3B82F6` - - 文字:白色,13px 粗体 - - 内边距:10px 20px - - 圆角:6px - - Hover:背景 `#2563EB` - - 状态:点击后显示加载动画,完成后显示 "✓ 连接成功" 或 "✗ 连接失败" - -- **右侧**: - - **[保存]** 按钮 - - 背景:品牌粉红 `#D91B5C` - - 文字:白色,13px 粗体 - - 内边距:10px 24px - - 圆角:6px - - Hover:背景 `#C2185B` - - 禁用状态(未测试连接时):opacity 0.5,pointer-events none - - - **[取消]** 按钮 - - 背景:透明,边框 1px `#D1D5DB` - - 文字:`#374151`,13px 粗体 - - 内边距:10px 24px - - 圆角:6px - - Hover:背景 `#F3F4F6` - -### 5.5 测试连接反馈 -- **进行中**:按钮显示加载动画(旋转圆形),文字变为 "测试中..." -- **成功**: - - 按钮变绿 `#10B981` - - 文字:`✓ 连接成功` - - 下方显示绿色提示信息:`API Key 有效,模型 gpt-4o 可用` - - 延迟 1.5 秒后,自动恢复为 "测试连接" 状态 -- **失败**: - - 按钮变红 `#EF4444` - - 文字:`✗ 连接失败` - - 下方显示红色错误信息:`API Key 无效或网络错误:Unauthorized` - - 用户可以点击"查看详情"展开错误日志 - ---- - -## 6. 空状态 - -### 6.1 无模型已配置 -``` -┌────────────────────────────────────────┐ -│ │ -│ [⊕ 大图标] │ -│ │ -│ 尚未配置模型 │ -│ │ -│ 通过添加你的第一个 AI 模型配置并开始。│ -│ 连接到 OpenAI、Anthropic 或本地实例。 │ -│ │ -│ [+ 添加你的第一个 AI 模型] │ -│ │ -└────────────────────────────────────────┘ -``` - -**样式**: -- **背景**:白色卡片,圆角 12px,边框 2px dashed `#E5E7EB` -- **内容对齐**:居中 -- **图标**:`⊕`,尺寸 48px,颜色 `#D1D5DB` -- **标题**:`尚未配置模型`,18px 粗体 600,颜色 `#1F2937`,下边距 8px -- **描述**:14px,颜色 `#6B7280`,行高 1.6 -- **按钮**:同"添加新模型"按钮样式 - ---- - -## 7. 交互动画与过渡 - -### 7.1 卡片加载动画 -- 新增卡片从底部滑入(300ms ease-out) -- 从 `translateY(20px), opacity: 0` 到 `translateY(0), opacity: 1` - -### 7.2 卡片删除动画 -- 向右滑出 + 淡出(300ms ease-in) -- 从 `translateX(0), opacity: 1` 到 `translateX(100px), opacity: 0` -- 删除前弹出确认对话框 - -### 7.3 状态指示灯脉冲 -- **已激活**:绿灯,脉冲动画 `scale: 1 ↔ 1.2`,2s 循环 -- **连接错误**:红灯,不闪烁,持续显示警告状态 - -### 7.4 对话框出现/消失 -- **打开**:从中心缩放 `scale: 0.9, opacity: 0` 到 `scale: 1, opacity: 1`,200ms ease-out -- **关闭**:反向,200ms ease-in -- **背景**:暗灰色 overlay `rgba(0, 0, 0, 0.5)`,同步过渡 - ---- - -## 8. 响应式设计 - -### 8.1 桌面版(≥ 1200px) -- 侧边栏固定宽度 280px -- 卡片最大宽度 800px -- 两列布局(侧栏 + 主内容) - -### 8.2 平板版(768px ~ 1199px) -- 侧边栏可折叠(汉堡菜单 ☰) -- 卡片最大宽度 600px -- 内容区内边距减少到 20px - -### 8.3 移动版(< 768px) -- 应用场景:桌面应用在小窗口下 -- 侧边栏隐藏,导航改为顶部标签栏 -- 卡片 100% 宽度,内边距 16px - ---- - -## 9. 键盘快捷键 & 无障碍 - -### 9.1 快捷键 -- **Tab**:在卡片和按钮间导航 -- **Enter**:激活聚焦元素(按钮、下拉框选项) -- **Escape**:关闭对话框或下拉菜单 -- **Ctrl+K**:快速打开"添加模型"对话框 - -### 9.2 屏幕阅读器支持 -- 所有按钮有 `aria-label` -- 卡片有 `role="region"` 和 `aria-labelledby="card-title"` -- 状态指示器有 `aria-live="polite"` 以通知状态变化 -- 对话框有 `role="dialog"` 和 `aria-modal="true"` - -### 9.3 颜色对比度 -- 所有文字与背景对比度 ≥ 4.5:1(符合 WCAG AA) -- 状态指示灯配备文字标签(不仅依赖颜色) - ---- - -## 10. 技术实现提示 - -### 10.1 React 组件结构 -```jsx - - - - - - {models.map(model => ( - - ))} - - {models.length === 0 && } - - - {showAddModal && ( - - )} - -``` - -### 10.2 状态管理建议 -- 使用 **Zustand** 或 **Jotai** 管理模型列表、当前选中模型 -- 使用 **React Query** 缓存 API 连接测试结果 -- 本地存储使用 **Electron 的 ipcMain** 与主进程通信,加密保存 API Key - -### 10.3 样式方案 -- **Tailwind CSS** + 自定义 Tailwind Config 定义颜色变量 -- **Framer Motion** 处理动画 -- 或使用 **Styled Components** + **react-spring** 实现更细致的动画 - -### 10.4 表单验证 -- API Key 输入框:前端显示 "最少 20 字符" 提示 -- 供应商选择:必选项 -- 测试连接成功后,"保存"按钮才可用 - ---- - -## 11. 设计资源清单 - -### 11.1 图标 -- 菱形 `◆`、圆形 `●`、空心圆 `◯`、警告三角 `⚠` -- 编辑 ✏、删除 🗑、收藏 ☆/★、设置 ⚙、加号 +、右箭头 →、眼睛 👁 - -### 11.2 颜色变量表 -```css -:root { - /* 品牌色 */ - --brand-primary: #D91B5C; - --brand-gradient: linear-gradient(135deg, #D91B5C 0%, #C2185B 100%); - - /* 背景 */ - --bg-page: #F5F7FA; - --bg-card: #FFFFFF; - --bg-input: #F9FAFB; - --bg-sidebar: var(--brand-gradient); - - /* 文字 */ - --text-primary: #1F2937; - --text-secondary: #6B7280; - --text-muted: #9CA3AF; - --text-white: #FFFFFF; - - /* 边框 */ - --border-default: #E5E7EB; - --border-dark: #D1D5DB; - - /* 状态色 */ - --success: #10B981; - --error: #EF4444; - --warning: #F59E0B; - --info: #3B82F6; -} -``` - -### 11.3 字体 -- 主字体:Noto Sans SC、PingFang SC(中文)、Inter(英文) -- 等宽字体(API Key 显示):JetBrains Mono、Fira Code - ---- - -## 12. 验收标准 - -### 12.1 视觉还原度 -- [ ] 侧边栏渐变色与原型一致 -- [ ] 导航项 active 状态有左侧竖线指示器 -- [ ] 卡片阴影和圆角符合规范 -- [ ] 状态指示器(●/◯/⚠)颜色准确 -- [ ] 模态对话框背景 overlay 正确显示 - -### 12.2 交互流畅性 -- [ ] 卡片加载动画平滑(60fps) -- [ ] 对话框打开/关闭无闪烁 -- [ ] 按钮 hover/click 反应 < 100ms -- [ ] 下拉菜单展开流畅 - -### 12.3 功能完整性 -- [ ] 可添加新模型 -- [ ] 模型连接测试正常工作 -- [ ] 删除模型弹出确认框 -- [ ] 设为默认后,卡片状态正确更新 -- [ ] 收藏/取消收藏功能正常 - -### 12.4 表单验证 -- [ ] 未输入 API Key 时"保存"按钮禁用 -- [ ] 连接失败时显示错误信息 -- [ ] 测试成功后自动填充模型信息 - -### 12.5 跨平台一致性 -- [ ] Windows 和 macOS 上侧边栏宽度一致 -- [ ] 字体渲染清晰 -- [ ] 颜色在两平台显示一致 - ---- - -**文档版本**:v1.0 -**最后更新**:2025-11-12 -**维护者**:产品设计团队 - diff --git a/desktop/spec/ui-design-03-dashboard.md b/desktop/spec/ui-design-03-dashboard.md deleted file mode 100644 index fb6c3aa..0000000 --- a/desktop/spec/ui-design-03-dashboard.md +++ /dev/null @@ -1,557 +0,0 @@ -# UI 设计文档:仪表板 / 主页(概览与对话列表) - -> **原型图参考**:Image 3 - 欢迎回来!(Dashboard 主页) -> **对应功能**:攻略对象列表、对话统计、最近对话、新建对话 - ---- - -## 1. 整体布局 - -### 1.1 页面属性 -- **页面类型**:主窗口内容区域 -- **尺寸**:全屏适配,最小 1200px × 800px -- **背景**:浅白色 `#F5F7FA` -- **滚动**:内容区支持垂直滚动 - -### 1.2 整体结构(分两列) -``` -┌─────────────────────────────────────────────────────────────────┐ -│ [侧边栏] [主内容区] │ -│ │ -│ ◆ 总览 ✓ 欢迎回来! │ -│ ◆ 攻略对象 这是你的对话项目快照。 │ -│ ◆ 对话编辑器 │ -│ ◆ LLM 配置 ┌─────────────────────────────┐ │ -│ ◆ 设置 │ 攻略对象: 12 │ │ -│ │ 对话: 89 │ │ -│ │ 分享: 256 │ │ -│ │ 故事标记: 42 │ │ -│ [帮助] └─────────────────────────────┘ │ -│ [报告问题] │ -│ [登出] ┌─────────────────────────────────────────┐│ -│ │ 最近对话 [实时助手] ││ -│ │ "我"与攻略对象的聊天日志摘要。 ││ -│ │ [新对话] ││ -│ │ ││ -│ │ 第1章:命运的相遇 ││ -│ │ 优衣在樱花丛中与优司见面,命运的奇缘... ││ -│ │ 攻略对象: [Miyu] [Akira] [Hana] [Yuki] ││ -│ │ 最后编辑: 2天前 ││ -│ │ ││ -│ │ 咖啡馆的误会 ││ -│ │ 一个关键的冲突场景。她与男主在咖啡馆... ││ -│ │ 攻略对象: [Miyu] [Akira] ││ -│ │ 最后编辑: 5天前 ││ -│ │ ││ -│ │ 图书馆的告白 ││ -│ │ 一个感人的逆转,优衣在学校图书馆学习... ││ -│ │ 攻略对象: [Miyu] ││ -│ │ 最后编辑: 1周前 ││ -│ │ ││ -│ │ [+ 创建新对话] ││ -│ │ 从头开始一段新对话。 ││ -│ │ ││ -│ └─────────────────────────────────────────┘│ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 2. 侧边栏导航(同 LLM Config 页) - -### 2.1 导航结构 -- 与 LLM 配置页相同的侧边栏设计 -- 当前高亮项:`◆ 总览 ✓` - -### 2.2 样式参考 -- 宽度:280px -- 背景:品牌粉红渐变 -- 参考 `ui-design-02-llm-config.md` 第 2 节 - ---- - -## 3. 顶部欢迎区 - -### 3.1 欢迎标题 -``` -欢迎回来! -这是你的对话项目快照。 -``` - -- **位置**:主内容区顶部,内边距 40px -- **背景**:白色 `#FFFFFF`,底部 1px 边框 `#E5E7EB` -- **标题**: - - 文字:`欢迎回来!` - - 字体:32px,粗体 700,颜色 `#1F2937` - - 下边距:12px -- **副标题**: - - 文字:`这是你的对话项目快照。` - - 字体:15px,颜色 `#6B7280` - - 下边距:0px - ---- - -## 4. 统计卡片区域 - -### 4.1 卡片布局 -``` -┌──────────────────┬──────────────────┬──────────────────┬──────────────────┐ -│ 攻略对象 │ 对话 │ 分享 │ 故事标记 │ -│ 12 │ 89 │ 256 │ 42 │ -└──────────────────┴──────────────────┴──────────────────┴──────────────────┘ -``` - -- **布局**:4 列网格,`gap: 16px` -- **容器**:内边距 40px(同欢迎区) -- **背景**:`#F5F7FA`(页面背景) - -### 4.2 单个统计卡片 -- **宽度**:calc(25% - 12px)(4 列等宽) -- **背景**:白色 `#FFFFFF` -- **圆角**:12px -- **内边距**:24px 20px -- **边框**:1px solid `#E5E7EB` -- **阴影**:`0 1px 3px rgba(0, 0, 0, 0.1)` -- **Hover**: - - 阴影升高到 `0 4px 12px rgba(0, 0, 0, 0.15)` - - 过渡 200ms ease - - 指针变为 pointer(可点击查看详情) - -### 4.3 卡片内容 -- **标签**(上): - - 文字:`攻略对象`、`对话`、`分享`、`故事标记` - - 字体:13px,颜色 `#6B7280` - - 下边距:8px - -- **数字**(下): - - 数值:`12`、`89`、`256`、`42` - - 字体:36px,粗体 700,颜色 `#1F2937` - - 对齐:左对齐 - -### 4.4 响应式 -- **宽度 ≥ 1200px**:4 列 -- **宽度 1000px ~ 1199px**:3 列(最后一列换行) -- **宽度 768px ~ 999px**:2 列 -- **宽度 < 768px**:1 列 - ---- - -## 5. 最近对话区域 - -### 5.1 区域容器 -- **内边距**:40px -- **背景**:`#F5F7FA`(页面背景) - -### 5.2 区域头部(标题 + 操作按钮) -``` -最近对话 [实时助手] [新对话] -"我"与攻略对象的聊天日志摘要。 -``` - -- **布局**:Flex row,`justify-content: space-between` -- **左侧**: - - **标题**:`最近对话`,22px 粗体 700,颜色 `#1F2937` - - **副标题**:`"我"与攻略对象的聊天日志摘要。`,13px,颜色 `#6B7280`,下边距 16px - -- **右侧**(操作按钮): - - **[实时助手]** 按钮 - - 尺寸:高 36px,宽 120px - - 背景:品牌粉红 `#D91B5C` - - 文字:白色,13px 粗体 - - 圆角:8px - - Hover:背景 `#C2185B` - - 左侧图标:⏱(时钟符号) - - - **[新对话]** 按钮 - - 尺寸:高 36px,宽 120px - - 背景:品牌粉红 `#D91B5C` - - 文字:白色,13px 粗体 - - 圆角:8px - - Hover:背景 `#C2185B` - - 左侧图标:+ (加号) - ---- - -## 6. 对话卡片列表 - -### 6.1 卡片整体容器 -- **背景**:白色 `#FFFFFF` -- **圆角**:12px -- **边框**:1px solid `#E5E7EB` -- **溢出**:`overflow: hidden`(圆角裁剪) -- **最大宽度**:900px -- **阴影**:`0 1px 3px rgba(0, 0, 0, 0.1)`,hover 升高到 `0 4px 12px rgba(0, 0, 0, 0.15)` - -### 6.2 单条对话卡片 -``` -┌─────────────────────────────────────────────────────────────┐ -│ 第1章:命运的相遇 │ -│ 优衣在樱花丛中与优司见面,命运的奇缘... │ -│ 攻略对象: [Miyu] [Akira] [Hana] [Yuki] │ -│ 最后编辑: 2天前 │ -└─────────────────────────────────────────────────────────────┘ -``` - -**布局方式**:垂直堆叠,相邻卡片间有 1px 分隔线 `#E5E7EB` - -**样式规范**: -- **内边距**:20px(上下左右) -- **背景**: - - 默认:白色 - - Hover:极浅灰 `#F9FAFB` -- **过渡**:背景色 200ms ease -- **指针**:pointer(可点击进入对话详情) - -#### 卡片头部(标题) -- **文字**:`第1章:命运的相遇` -- **字体**:18px,粗体 600,颜色 `#1F2937` -- **下边距**:8px - -#### 卡片主体(描述) -- **文字**:`优衣在樱花丛中与优司见面,命运的奇缘...` -- **字体**:14px,颜色 `#6B7280` -- **行高**:1.6 -- **最大行数**:2 行,超出显示省略号 `...` -- **下边距**:12px - -#### 卡片底部(元数据) -- **布局**:Flex row,`justify-content: space-between, align-items: center` -- **左侧**: - - **攻略对象标签** - - 格式:`攻略对象: [Miyu] [Akira] [Hana] [Yuki]` - - 每个标签为 inline-pill: - - 背景:`rgba(217, 27, 92, 0.1)`(极淡粉) - - 文字:`#D91B5C`(品牌粉色),11px - - 内边距:4px 8px - - 圆角:12px - - 间距:4px - - 超过 3 个标签时,显示 `+{剩余数}` 标签,点击展开全部 - - 字体标签:"攻略对象:" 为 11px,颜色 `#9CA3AF`(灰色) - -- **右侧**: - - **最后编辑时间** - - 格式:`最后编辑: 2天前`(相对时间) - - 字体:12px,颜色 `#9CA3AF` - - Hover 时显示绝对时间(如 "2025-11-10 14:32")作为 tooltip - -#### 交互行为 -- **点击卡片**:进入对话详情页面(对应 Image 4) -- **右键菜单**(可选): - - 编辑对话名称 - - 删除对话 - - 复制对话链接 - - 导出对话(JSON 或 PDF) - ---- - -## 7. 空状态 - -### 7.1 无对话记录 -显示条件:用户未创建任何对话 - -``` -┌────────────────────────────────┐ -│ │ -│ [⊕ 大图标] │ -│ │ -│ 还没有对话 │ -│ │ -│ 创建第一个对话,开始攻略之旅。 │ -│ │ -│ [+ 创建新对话] │ -│ │ -└────────────────────────────────┘ -``` - -**样式**: -- **背景**:白色卡片,圆角 12px,边框 2px dashed `#E5E7EB` -- **内容对齐**:居中 -- **图标**:`⊕`,48px,颜色 `#D1D5DB` -- **标题**:`还没有对话`,20px 粗体 600,颜色 `#1F2937`,下边距 8px -- **描述**:14px,颜色 `#6B7280` -- **按钮**:`+ 创建新对话`,品牌粉红背景 - ---- - -## 8. 创建新对话模态框 - -### 8.1 触发方式 -点击 "[新对话]" 按钮或空状态中的 "[+ 创建新对话]" 按钮 - -### 8.2 对话框属性 -- **宽度**:550px -- **背景**:白色 -- **圆角**:12px -- **阴影**:`0 10px 40px rgba(0, 0, 0, 0.2)` - -### 8.3 对话框结构 -``` -┌────────────────────────────────────────────┐ -│ 创建新对话 [×] │ -├────────────────────────────────────────────┤ -│ 对话标题(选填): │ -│ ┌──────────────────────────────────────┐ │ -│ │ 例:第1章:命运的相遇 │ │ -│ └──────────────────────────────────────┘ │ -│ │ -│ 描述(选填): │ -│ ┌──────────────────────────────────────┐ │ -│ │ 例:主角与女主在樱花树下相遇... │ │ -│ │ │ │ -│ └──────────────────────────────────────┘ │ -│ │ -│ 涉及的攻略对象: │ -│ ◇ Miyu (暧昧) │ -│ ◇ Akira (朋友) │ -│ ◇ Hana (竞争对手) │ -│ ◇ Yuki (闺蜜) │ -│ │ -│ [创建] [取消] │ -└────────────────────────────────────────────┘ -``` - -### 8.4 字段定义 - -#### 对话标题输入 -- **标签**:`对话标题(选填):`,13px 粗体 600 -- **输入框**: - - 高 40px - - 背景 `#F9FAFB`,边框 1px `#D1D5DB` - - 圆角 6px - - Placeholder:`例:第1章:命运的相遇`,颜色 `#9CA3AF` - - Focus:边框 `#D91B5C`,阴影 `0 0 0 3px rgba(217, 27, 92, 0.1)` - -#### 描述输入 -- **标签**:`描述(选填):`,13px 粗体 600 -- **文本框**: - - 高 100px - - 背景 `#F9FAFB`,边框 1px `#D1D5DB` - - 圆角 6px - - Placeholder:`例:主角与女主在樱花树下相遇...` - - 可调整大小(右下角缩放手柄) - - Focus:边框 `#D91B5C` - -#### 攻略对象选择 -- **标签**:`涉及的攻略对象:`,13px 粗体 600,下边距 12px -- **对象列表**: - - 显示应用中已有的所有攻略对象 - - 每个对象为一个 checkbox + 标签 - - **Checkbox 样式**: - - 未选中:空心圆 `◇`,颜色 `#D1D5DB` - - 选中:实心圆 `◆`,颜色 `#D91B5C` - - **标签**: - - 文字:`Miyu (暧昧)` - - 字体:13px,颜色 `#374151` - - 左边距 8px(checkbox 后) - - **行间距**:12px - -### 8.5 按钮栏 -``` - [创建] [取消] -``` - -- **[创建]** 按钮 - - 背景:品牌粉红 `#D91B5C` - - 文字:白色,13px 粗体 - - 内边距:10px 32px - - 圆角:6px - - Hover:背景 `#C2185B` - - 禁用:标题为空时,opacity 0.5,pointer-events none - -- **[取消]** 按钮 - - 背景:透明,边框 1px `#D1D5DB` - - 文字:`#374151`,13px 粗体 - - 内边距:10px 32px - - 圆角:6px - - Hover:背景 `#F3F4F6` - ---- - -## 9. 实时助手功能(侧板) - -### 9.1 侧板触发 -点击 "[实时助手]" 按钮时,从右侧滑出一个侧板 - -### 9.2 侧板属性 -- **宽度**:350px -- **位置**:固定定位,右侧,从 `translateX(100%)` 滑入到 `translateX(0)` -- **高度**:100vh -- **背景**:白色 `#FFFFFF`,左边 1px 分隔线 `#E5E7EB` -- **z-index**:确保在主内容上方(但低于模态框) -- **动画**:300ms ease-out - -### 9.3 侧板内容(可选扩展功能,暂简化) -``` -┌────────────────────────────┐ -│ 实时助手 [×]│ -├────────────────────────────┤ -│ │ -│ [录制按钮] │ -│ │ -│ 对话建议: │ -│ ... │ -│ │ -└────────────────────────────┘ -``` - -- 暂定为简化版本,功能延后:保留 UI 框架,内容可扩展 - ---- - -## 10. 页面转换动画 - -### 10.1 卡片加载动画 -- 对话卡片从底部以 stagger 效果进入(300ms ease-out) -- 每张卡片间隔 50ms - -### 10.2 对话框打开/关闭 -- **打开**:`scale: 0.9, opacity: 0` 到 `scale: 1, opacity: 1`,200ms ease-out -- **关闭**:反向,200ms ease-in -- **背景 overlay**:`rgba(0, 0, 0, 0)` 到 `rgba(0, 0, 0, 0.5)`,同步过渡 - ---- - -## 11. 响应式设计 - -### 11.1 桌面版(≥ 1200px) -- 侧边栏固定 280px -- 统计卡片 4 列 -- 对话卡片最大宽度 900px - -### 11.2 平板版(768px ~ 1199px) -- 侧边栏可折叠(汉堡菜单) -- 统计卡片 2 列 -- 对话卡片宽度自适应 - -### 11.3 移动/小窗口版(< 768px) -- 侧边栏隐藏,导航改为顶部 -- 统计卡片 1 列 -- 对话卡片全宽 - ---- - -## 12. 键盘快捷键 & 无障碍 - -### 12.1 快捷键 -- **Ctrl+N**:打开"新对话"对话框 -- **Ctrl+K**:搜索对话 -- **Enter**:激活聚焦的卡片或按钮 -- **Escape**:关闭对话框或侧板 - -### 12.2 屏幕阅读器 -- 统计卡片有 `role="region"` 和 `aria-label` -- 对话卡片有 `role="button"` 或 `` 标签(可聚焦、可激活) -- 模态框有 `role="dialog"` 和 `aria-modal="true"` - -### 12.3 颜色对比度 -- 所有文字与背景对比度 ≥ 4.5:1 - ---- - -## 13. 技术实现提示 - -### 13.1 React 组件结构 -```jsx - - - - - - - {conversations.length > 0 ? ( - - ) : ( - - )} - - - - {showNewConversationModal && ( - - )} - - {showLiveAssistant && ( - - )} - -``` - -### 13.2 样式方案 -- **Tailwind CSS** + 自定义变量 -- **Framer Motion** 处理卡片和模态框动画 - -### 13.3 状态管理 -- 使用 **Zustand** 或 **Jotai** 管理对话列表 -- 使用 **React Query** 缓存对话统计数据 -- 模态框和侧板状态独立管理 - ---- - -## 14. 设计资源清单 - -### 14.1 图标 -- 时钟 ⏱、加号 +、×、菱形 ◇◆、大加号 ⊕、复选框 ☑ - -### 14.2 颜色变量表 -```css -:root { - --brand-primary: #D91B5C; - --brand-gradient: linear-gradient(135deg, #D91B5C 0%, #C2185B 100%); - - --bg-page: #F5F7FA; - --bg-card: #FFFFFF; - --bg-input: #F9FAFB; - --bg-sidebar: var(--brand-gradient); - - --text-primary: #1F2937; - --text-secondary: #6B7280; - --text-muted: #9CA3AF; - - --border-default: #E5E7EB; - --border-dark: #D1D5DB; - - --tag-bg: rgba(217, 27, 92, 0.1); - --tag-text: #D91B5C; -} -``` - ---- - -## 15. 验收标准 - -### 15.1 视觉还原度 -- [ ] 欢迎标题和副标题排版正确 -- [ ] 统计卡片网格布局和对齐准确 -- [ ] 对话卡片标题、描述、标签、时间排版清晰 -- [ ] 攻略对象标签颜色与原型一致 -- [ ] 空状态图标和文字居中 - -### 15.2 交互流畅性 -- [ ] 卡片加载 stagger 动画平滑(60fps) -- [ ] 对话框打开/关闭无闪烁 -- [ ] 按钮 hover/click 反应 < 100ms -- [ ] 侧板滑入/滑出流畅 - -### 15.3 功能完整性 -- [ ] 新建对话弹窗正常打开 -- [ ] 对话标题和描述输入正常 -- [ ] 攻略对象复选框可正常选中/取消 -- [ ] 创建对话后,卡片出现在列表顶部 -- [ ] 点击对话卡片进入详情页 -- [ ] 实时助手侧板可打开/关闭 - -### 15.4 跨平台一致性 -- [ ] Windows 和 macOS 侧边栏宽度一致 -- [ ] 统计卡片在两平台渲染一致 -- [ ] 字体清晰 - ---- - -**文档版本**:v1.0 -**最后更新**:2025-11-12 -**维护者**:产品设计团队 - diff --git a/desktop/spec/ui-design-04-conversation-detail.md b/desktop/spec/ui-design-04-conversation-detail.md deleted file mode 100644 index 026ecb5..0000000 --- a/desktop/spec/ui-design-04-conversation-detail.md +++ /dev/null @@ -1,614 +0,0 @@ -# UI 设计文档:对话详情 / 分析页面(AI 复盘与数据化反馈) - -> **原型图参考**:Image 4 - 与 Miyu 的对话(对话详情与分析) -> **对应功能**:对话内容展示、AI 分析、好感度曲线、复盘建议 - ---- - -## 1. 整体布局 - -### 1.1 页面属性 -- **页面类型**:主窗口内容区域 -- **尺寸**:全屏适配,最小 1200px × 800px -- **背景**:浅白色 `#F5F7FA` -- **布局**:三列设计(侧栏 + 中间对话 + 右侧分析) - -### 1.2 整体结构 -``` -┌──────────────────────────────────────────────────────────────────┐ -│ [侧边栏] [中间对话区] [右侧分析板] │ -│ │ -│ ◆ 总览 与 Miyu 的对话 ✓ [AI 分析与编辑] │ -│ ◆ 攻略对象 这是你的对话项目快照。 │ -│ ◆ 对话编辑器 [AI 洞察] │ -│ ◆ LLM 配置 ┌────────────────┐ ┌──────────────────┐ │ -│ ◆ 设置 │ [攻略对象] │ │ 你似乎平衡了 │ │ -│ │ [对话] │ │ 主动性与被动性 │ │ -│ │ [分享] │ │ 很好! │ │ -│ │ [故事标记] │ │ │ │ -│ └────────────────┘ │ [编辑消息] │ │ -│ [帮助] │ │ │ -│ [报告问题] ┌────────────────┐ │ [编辑消息] │ │ -│ [登出] │ Miyu: 呃,真的吗?│ │ │ │ -│ │ 不过这听起来...│ │ [暂无推荐] │ │ -│ │ 我当然愿意! │ │ │ │ -│ │ │ │ [进一步建议] │ │ -│ │ 我: 我当然愿意│ │ │ │ -│ │ 让我试试... │ │ │ │ -│ │ │ │ [AI 复盘] │ │ -│ │ Miyu: 听起来...│ │ 你做得很棒... │ │ -│ │ 对我很特别 │ │ │ │ -│ │ │ │ [查看完整分析] │ │ -│ │ [💭 思考中] │ │ │ │ -│ │ [+ 添加消息] │ │ │ │ -│ └────────────────┘ └──────────────────┘ -│ -└──────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 2. 侧边栏导航(同前) - -### 2.1 结构 -- 与 LLM Config、Dashboard 相同的侧边栏 -- 当前高亮:`◆ 对话编辑器` -- 参考 `ui-design-02-llm-config.md` 第 2 节 - ---- - -## 3. 顶部标题栏(中间列) - -### 3.1 标题栏布局 -``` -与 Miyu 的对话 ✓ [⏮ 上一个] [下一个 ⏭] [⋮ 更多] -这是你的对话项目快照。 -``` - -- **高度**:80px -- **背景**:白色 `#FFFFFF` -- **边框**:底部 1px solid `#E5E7EB` -- **内边距**:20px 40px -- **布局**:Flexbox 列方向,上方标题行,下方按钮/时间行 - -### 3.2 标题行 -- **布局**:Flex row,`justify-content: space-between, align-items: center` -- **左侧**: - - **标题文字**:`与 Miyu 的对话`,24px 粗体 700,颜色 `#1F2937` - - **状态指示**:✓ 绿色徽章,表示对话已完成/已保存 - - 字体:14px,颜色 `#10B981` - -- **右侧**(操作按钮): - - **[⏮ 上一个]**:跳转到上一条对话,禁用态 opacity 0.5 - - **[下一个 ⏭]**:跳转到下一条对话,禁用态 opacity 0.5 - - **[⋮ 更多]**:下拉菜单 - - 编辑对话标题 - - 编辑对话描述 - - 删除对话 - - 导出对话(JSON/PDF) - - 查看历史版本 - - 按钮样式:高 36px,宽 100px,背景透明边框,圆角 6px,Hover 背景 `#F3F4F6` - -### 3.3 副标题行 -- **文字**:`这是你的对话项目快照。` -- **字体**:14px,颜色 `#6B7280` -- **右侧**(元数据): - - 创建时间:`创建于: 2025-11-10 14:32` - - 最后编辑:`最后编辑: 2小时前` - - 字体:12px,颜色 `#9CA3AF` - ---- - -## 4. 中间对话内容区 - -### 4.1 容器属性 -- **背景**:白色 `#FFFFFF` -- **宽度**:计算值 `calc(100% - 280px - 350px - 80px)`(侧栏 + 右板 + 间距) -- **高度**:`calc(100vh - 80px - 60px)`(标题栏 + 底部操作栏) -- **内边距**:20px(内部消息间距) -- **边框**:右侧 1px solid `#E5E7EB`(分隔右侧分析板) -- **滚动**:`overflow-y: auto`,自定义滚动条 - -### 4.2 对话气泡(同 Chat Window) -参考 `ui-design-01-chat-window.md` 第 3 节 - -**调整**: -- **消息时间戳**:每条消息下方显示,格式 `14:32` -- **消息分组**:按天分组,显示日期分隔线(如 "2025-11-10") -- **关键标记**: - - 重要消息(由 AI 标记):右上角 ⭐,深度灰色背景轻度强调 - - 转折点(好感度大幅变化):消息右侧显示好感度变化 `+20` 或 `-5` - -### 4.3 中间插入元素 - -#### AI 思考中指示器 -``` -[思考图标] AI 分析中... -``` -- 显示在对话列表中间 -- 高度 40px,背景 `rgba(59, 130, 246, 0.1)` -- 文字:`AI 分析中...`,13px,颜色蓝色 -- 脉冲动画 - -#### 添加消息按钮 -``` -[+ 添加消息] -``` -- 位置:对话列表底部 -- 样式:按钮样式,高 40px,背景 `rgba(217, 27, 92, 0.1)`,文字粉红色 -- 点击后打开消息编辑对话框 - ---- - -## 5. 底部操作栏(中间列) - -### 5.1 操作栏布局 -``` -[😊] [⏸] [✨] [⚙] [💾 保存] -``` - -- **高度**:64px -- **背景**:白色 `#FFFFFF`,上方 1px 分隔线 -- **内边距**:16px -- **布局**:Flexbox 横向,`justify-content: center, gap: 24px` -- **右侧**:"保存"按钮靠右对齐 - -### 5.2 按钮定义 -- **[😊]、[⏸]、[✨]、[⚙]**:同 Chat Window -- **[💾 保存]**: - - 背景:品牌粉红 `#D91B5C` - - 文字:白色,13px 粗体 - - 内边距:10px 24px - - 圆角:6px - - Hover:背景 `#C2185B` - - 点击后短暂变为 "✓ 已保存",1 秒后恢复 - ---- - -## 6. 右侧分析板 - -### 6.1 板块容器 -- **宽度**:350px(固定) -- **背景**:`#F9FAFB`(极浅灰) -- **高度**:100vh -- **内边距**:20px -- **滚动**:`overflow-y: auto` -- **边框**:左侧 1px solid `#E5E7EB` - -### 6.2 标题栏 -``` -AI 分析与编辑 -``` -- **文字**:`AI 分析与编辑` -- **字体**:16px 粗体 600,颜色 `#1F2937` -- **下边距**:20px -- **下方有 1px 分隔线** `#E5E7EB` - -### 6.3 分析卡片(多个) - -#### 卡片容器 -``` -┌─────────────────────────────┐ -│ 你似乎平衡了主动性与被动性 │ -│ 很好! │ -│ │ -│ [编辑消息] │ -└─────────────────────────────┘ -``` - -**样式**: -- **背景**:白色 `#FFFFFF` -- **圆角**:8px -- **边框**:1px solid `#E5E7EB` -- **内边距**:16px -- **间距**:卡片之间 12px -- **阴影**:`0 1px 2px rgba(0, 0, 0, 0.05)` -- **Hover**:阴影升高到 `0 2px 4px rgba(0, 0, 0, 0.1)` - -#### 卡片内容 -- **分析文本**:14px,行高 1.6,颜色 `#374151` -- **icon**(可选):卡片左上角小图标(如 💭、💡、⚠ 等),尺寸 20px - -#### 编辑按钮 -- **位置**:卡片底部,右对齐 -- **文字**:`[编辑消息]` -- **样式**: - - 背景:蓝色 `#3B82F6` - - 文字:白色,12px 粗体 - - 内边距:6px 12px - - 圆角:4px - - Hover:背景 `#2563EB` - -### 6.4 分析卡片类型与显示规则 - -| 类型 | 图标 | 样本文本 | 触发条件 | -|------|------|------|------| -| 正面反馈 | 💭 | 你似乎平衡了主动性与被动性,很好! | AI 判定消息质量高 | -| 中立观察 | 💡 | 暂无推荐 | 无特殊建议或中性回应 | -| 改进建议 | ⚠️ | 在对方提到"最近工作有点累"时,你转移了话题。建议:多关心对方状态。 | AI 检测到可改进点 | -| 关键转折 | 🎯 | 进一步建议:下次对话中提及这个话题。 | 消息引发好感度大幅变化 | - -### 6.5 AI 复盘分析 - -位置:分析卡片集合底部 - -``` -┌──────────────────────────────────┐ -│ AI 复盘 │ -│ ────────────────────────────────│ -│ 你做得很棒,这次对话中你... │ -│ ...(省略部分内容)... │ -│ ...保持了自然和真诚。 │ -│ │ -│ [查看完整分析] → │ -└──────────────────────────────────┘ -``` - -**样式**: -- **背景**:品牌粉色极淡版 `rgba(217, 27, 92, 0.05)` -- **圆角**:8px -- **边框**:1px solid `rgba(217, 27, 92, 0.2)` -- **内边距**:16px -- **标题**:`AI 复盘`,14px 粗体 600,颜色 `#D91B5C` -- **下方分隔线**:1px `rgba(217, 27, 92, 0.1)` -- **摘要文本**: - - 字体:13px,行高 1.6,颜色 `#374151` - - 最多显示 3 行,超出显示省略号 -- **[查看完整分析] →**: - - 链接样式,文字 `#D91B5C`,13px - - Hover:下划线 - - 点击打开全屏分析页面(或模态框) - ---- - -## 7. 好感度曲线(对话详情页变体) - -### 7.1 触发方式 -右侧分析板有一个 "📊 好感度变化" 折叠卡片(可选显示) - -``` -┌──────────────────────────┐ -│ 📊 好感度变化 ▼ │ ← 点击展开/折叠 -├──────────────────────────┤ -│ ┌────────────────────┐ │ -│ │ [曲线图] │ │ -│ │ 50 ─────────────→ │ │ -│ │ ↗ ↗ │ │ -│ │ 70 │ │ -│ │ (当前分数) │ │ -│ └────────────────────┘ │ -│ 本次对话好感度变化: │ -│ 初始: 50 → 最终: 70 │ -│ 变化: +20 │ -└──────────────────────────┘ -``` - -**样式**: -- **折叠头**:背景 `#F3F4F6`,高 44px,圆角 8px(仅顶部),内边距 12px -- **展开内容**:背景白色,圆角 8px(仅底部),内边距 16px -- **曲线图表**: - - 使用简单的 SVG 或 Canvas 绘制 - - x 轴:对话阶段(消息编号) - - y 轴:好感度分数(0-100) - - 线条颜色:渐变 `#3B82F6` → `#D91B5C` - - 圆点标记关键转折点 - - 宽度:100%,高度 150px - -### 7.2 统计数据展示 -- **初始好感度**:50 -- **最终好感度**:70 -- **总变化**:+20 -- **高峰**:72(第 8 条消息后) -- **低谷**:48(第 3 条消息后) - ---- - -## 8. 消息编辑模态框 - -### 8.1 触发方式 -- 点击"[编辑消息]"按钮 -- 或在中间对话区右键点击消息气泡选择"编辑" - -### 8.2 对话框属性 -- **宽度**:600px -- **背景**:白色 -- **圆角**:12px -- **阴影**:`0 10px 40px rgba(0, 0, 0, 0.2)` - -### 8.3 对话框结构 -``` -┌────────────────────────────────────────────┐ -│ 编辑消息 [×] │ -├────────────────────────────────────────────┤ -│ 角色: │ -│ ◎ Miyu ◎ 我 (Player) │ -│ │ -│ 消息内容: │ -│ ┌──────────────────────────────────────┐ │ -│ │ 听起来真不错!也可以这么说,我听说...│ │ -│ │ │ │ -│ └──────────────────────────────────────┘ │ -│ │ -│ 标记为关键:☐ 此消息为攻略关键转折点 │ -│ │ -│ [保存修改] [取消] │ -└────────────────────────────────────────────┘ -``` - -### 8.4 字段定义 - -#### 角色选择 -- **格式**:单选按钮(Radio) -- **选项**: - - ◎ Miyu(对方) - - ◎ 我 (Player)(用户自己) -- **默认**:根据被编辑消息的角色自动选中 -- **不可改变对方为用户**(防止数据混乱) - -#### 消息内容 -- **标签**:`消息内容:`,13px 粗体 -- **文本框**: - - 高 120px - - 背景 `#F9FAFB`,边框 1px `#D1D5DB` - - 圆角 6px - - 可调整大小 - - Focus:边框 `#D91B5C` - -#### 关键标记复选框 -- **样式**:标准 checkbox + 标签 -- **标签**:`此消息为攻略关键转折点`,13px,颜色 `#374151` -- **选中时**:右侧显示小星标 ⭐ -- **影响**:选中后,该消息在对话区会显示星标,在分析中优先级提高 - -### 8.5 按钮栏 -- **[保存修改]**:品牌粉红,保存后关闭对话框 -- **[取消]**:透明边框,关闭不保存 - ---- - -## 9. 全屏分析页面(可选扩展) - -### 9.1 触发方式 -点击右侧分析板中的 "[查看完整分析] →" - -### 9.2 页面内容 -``` -[返回] 完整 AI 分析报告 [导出为 PDF] - -===================================================== - -对话分析报告 - -对话标题:与 Miyu 的对话 -对话日期:2025-11-10 14:32 -分析时间:2025-11-10 15:45 - -───────────────────────────────────────────────── - -做得很好的地方 ✓ -• 及时回应对方邀约,表现出兴趣,效果:好感度 +15 -• 主动提及共同话题(旅游和咖啡),拉近距离,效果:好感度 +8 -• ... - -可以改进的地方 ⚠ -• 在对方提到"最近工作有点累"时,你转移了话题。 - 建议:多关心对方状态,增进情感连接。 -• 回应长度偏短,缺乏深度。 - 建议:加入 1-2 个细节或真诚的感受。 - -下次对话建议 💡 -1. 主动询问对方的工作情况(因为之前她提过压力) -2. 分享你最近的经历,建立相互理解 -3. 邀请对方参加共同感兴趣的活动 - -═════════════════════════════════════════════════ - -好感度变化详解 - -初始好感度:50 -最终好感度:70 -总变化:+20 - -过程分析: -• 第 1-3 条消息:好感度 50 → 55(缓慢上升) - 原因:互动开放,但缺乏特殊吸引力 -• 第 4-6 条消息:好感度 55 → 70(快速上升) - 原因:话题契合度高,双方共鸣增加 -• ... - -═════════════════════════════════════════════════ - -[导出为 PDF] [返回] -``` - -**样式**: -- 单列布局,宽度 900px,居中 -- 背景:白色 -- 排版:清晰分段,标题粗体 18px,正文 14px -- 导出按钮:右上固定或底部居中 - ---- - -## 10. 交互动画 - -### 10.1 右侧分析板入场 -- 从右侧 `translateX(100%)` 滑入到 `translateX(0)`,300ms ease-out - -### 10.2 分析卡片加载 -- Stagger 效果,每张卡片延迟 50ms -- 从下方 `translateY(20px), opacity: 0` 到 `translateY(0), opacity: 1` - -### 10.3 好感度曲线动画 -- 折叠卡片展开时,曲线图从 0% 宽度动画到 100%,500ms ease-out - ---- - -## 11. 响应式设计 - -### 11.1 桌面版(≥ 1400px) -- 三列布局:侧栏 280px + 中间 flex + 右板 350px -- 对话内容全量显示 - -### 11.2 平板版(1000px ~ 1399px) -- 三列压缩:侧栏可折叠,右板宽度减少到 300px - -### 11.3 小屏版(< 1000px) -- 右侧分析板改为上滑模态框(Sheet Modal) -- 对话内容占据大部分宽度 - ---- - -## 12. 键盘快捷键 & 无障碍 - -### 12.1 快捷键 -- **Ctrl+S**:保存对话 -- **Ctrl+E**:编辑当前消息 -- **Ctrl+Z**:撤销编辑 -- **Escape**:关闭编辑对话框或关闭分析板 - -### 12.2 屏幕阅读器 -- 消息气泡有 `role="article"` 和 `aria-label`(含消息主体) -- 分析卡片有 `role="region"` 和 `aria-labelledby` -- 按钮有 `aria-label` 和 `aria-pressed`(切换类按钮) - -### 12.3 颜色对比度 -- 所有文字与背景对比度 ≥ 4.5:1 - ---- - -## 13. 技术实现提示 - -### 13.1 React 组件结构 -```jsx - - - - - - - - - - - - - - - - {showEditMessageModal && ( - - )} - - {showFullAnalysis && ( - - )} - -``` - -### 13.2 数据结构 -```typescript -interface Message { - id: string; - role: 'user' | 'partner'; - content: string; - timestamp: Date; - isKeyTurningPoint: boolean; - sentimentImpact: number; // -20 to +20 -} - -interface Analysis { - type: 'positive' | 'neutral' | 'improvement' | 'turning_point'; - text: string; - messageId?: string; - icon: string; -} - -interface ConversationDetail { - id: string; - title: string; - partnerId: string; - messages: Message[]; - analyses: Analysis[]; - aiReview: string; - sentimentCurve: number[]; // 好感度序列 -} -``` - -### 13.3 样式方案 -- **Tailwind CSS** + 自定义变量 -- **Recharts** 或 **Chart.js** 绘制好感度曲线 - -### 13.4 状态管理 -- 使用 **Zustand** 或 **Jotai** 管理对话内容和编辑状态 -- 使用 **React Query** 缓存分析数据 - ---- - -## 14. 设计资源清单 - -### 14.1 图标 -- 返回 ⏮、前进 ⏭、更多 ⋮、保存 💾、思考 💭、灯泡 💡、警告 ⚠、目标 🎯、图表 📊、导出 📄 - -### 14.2 颜色变量表 -```css -:root { - --brand-primary: #D91B5C; - --brand-light: rgba(217, 27, 92, 0.05); - - --bg-page: #F5F7FA; - --bg-card: #FFFFFF; - --bg-input: #F9FAFB; - --bg-chart: #F9FAFB; - - --text-primary: #1F2937; - --text-secondary: #6B7280; - --text-muted: #9CA3AF; - - --border-default: #E5E7EB; - - --status-positive: #10B981; - --status-neutral: #6B7280; - --status-improvement: #F59E0B; - --status-turning: #D91B5C; -} -``` - ---- - -## 15. 验收标准 - -### 15.1 视觉还原度 -- [ ] 标题栏与副标题排版正确 -- [ ] 消息气泡格式(含时间、星标、好感度变化)完整 -- [ ] 右侧分析卡片样式一致 -- [ ] 好感度曲线图表清晰 -- [ ] 编辑消息对话框表单正确 - -### 15.2 交互流畅性 -- [ ] 右侧分析板滑入/滑出流畅 -- [ ] 分析卡片加载 stagger 动画平滑 -- [ ] 曲线图展开动画自然 -- [ ] 消息编辑对话框打开/关闭无闪烁 - -### 15.3 功能完整性 -- [ ] 消息可编辑,修改后实时显示 -- [ ] 关键标记可正确设置/取消 -- [ ] 分析卡片内容准确反映对话内容 -- [ ] AI 复盘分析文字清晰 -- [ ] 查看完整分析可正确打开全屏页面 - -### 15.4 跨平台一致性 -- [ ] Windows 和 macOS 三列布局对齐 -- [ ] 字体渲染清晰 -- [ ] 图表在两平台显示一致 - ---- - -**文档版本**:v1.0 -**最后更新**:2025-11-12 -**维护者**:产品设计团队 - diff --git a/desktop/spec/ui-design-components.md b/desktop/spec/ui-design-components.md deleted file mode 100644 index 5a4251b..0000000 --- a/desktop/spec/ui-design-components.md +++ /dev/null @@ -1,1001 +0,0 @@ -# UI 组件库设计文档(Reusable Components) - -> **用途**:跨页面复用的基础组件、容器组件和业务组件规范 -> **框架**:React + Tailwind CSS + Framer Motion -> **设计工具**:Figma(组件库系统) - ---- - -## 1. 设计系统基础 - -### 1.1 设计令牌(Design Tokens) - -#### 颜色系统 -```css -/* 品牌色 */ ---color-brand-primary: #D91B5C; /* 品牌粉红 */ ---color-brand-primary-dark: #C2185B; ---color-brand-primary-light: rgba(217, 27, 92, 0.1); - -/* 中性色 */ ---color-gray-50: #F9FAFB; ---color-gray-100: #F3F4F6; ---color-gray-200: #E5E7EB; ---color-gray-300: #D1D5DB; ---color-gray-400: #9CA3AF; ---color-gray-500: #6B7280; ---color-gray-600: #4B5563; ---color-gray-700: #374151; ---color-gray-800: #1F2937; ---color-gray-900: #111827; - -/* 语义色 */ ---color-success: #10B981; ---color-warning: #F59E0B; ---color-error: #EF4444; ---color-info: #3B82F6; - -/* 深色模式补充 */ ---color-dark-bg: #1F1F1F; ---color-dark-surface: #2A2A2A; ---color-dark-text: #E5E7EB; -``` - -#### 尺寸系统 -```css -/* 间距 */ ---spacing-xs: 4px; ---spacing-sm: 8px; ---spacing-md: 16px; ---spacing-lg: 24px; ---spacing-xl: 32px; ---spacing-2xl: 40px; - -/* 圆角 */ ---radius-xs: 4px; ---radius-sm: 6px; ---radius-md: 8px; ---radius-lg: 12px; ---radius-xl: 16px; ---radius-full: 9999px; - -/* 阴影 */ ---shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05); ---shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); ---shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); ---shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.15); ---shadow-xl: 0 10px 40px rgba(0, 0, 0, 0.2); - -/* 字体 */ ---font-family-sans: 'Noto Sans SC', 'PingFang SC', Inter, sans-serif; ---font-family-mono: 'JetBrains Mono', 'Fira Code', monospace; - -/* 字体尺寸 */ ---font-size-xs: 11px; ---font-size-sm: 12px; ---font-size-base: 13px; ---font-size-md: 14px; ---font-size-lg: 15px; ---font-size-xl: 16px; ---font-size-2xl: 18px; ---font-size-3xl: 20px; ---font-size-4xl: 24px; ---font-size-5xl: 28px; ---font-size-6xl: 32px; ---font-size-7xl: 36px; - -/* 行高 */ ---line-height-tight: 1.2; ---line-height-normal: 1.5; ---line-height-relaxed: 1.6; ---line-height-loose: 1.8; -``` - -#### 动画曲线 -```css ---ease-in: ease-in; ---ease-out: ease-out; ---ease-in-out: ease-in-out; ---ease-linear: linear; ---ease-custom-spring: cubic-bezier(0.25, 0.46, 0.45, 0.94); -``` - ---- - -## 2. 基础组件库(Atomic Components) - -### 2.1 Button 组件 - -#### 属性定义 -```tsx -interface ButtonProps { - variant?: 'primary' | 'secondary' | 'outline' | 'ghost'; - size?: 'xs' | 'sm' | 'md' | 'lg'; - disabled?: boolean; - loading?: boolean; - fullWidth?: boolean; - children: React.ReactNode; - onClick?: () => void; - className?: string; -} -``` - -#### 样式规范 - -| Variant | 背景 | 文字色 | 边框 | Hover 背景 | -|---------|------|-------|------|-----------| -| primary | `#D91B5C` | 白色 | 无 | `#C2185B` | -| secondary | `#F3F4F6` | `#374151` | `#E5E7EB` | `#E5E7EB` | -| outline | 透明 | `#374151` | `#D1D5DB` | `#F9FAFB` | -| ghost | 透明 | `#374151` | 无 | 无色 | - -| Size | 高度 | 内边距 | 字体 | 圆角 | -|------|------|-------|------|------| -| xs | 28px | 6px 12px | 11px | 4px | -| sm | 32px | 8px 16px | 12px | 6px | -| md | 40px | 10px 20px | 14px | 8px | -| lg | 48px | 12px 24px | 16px | 8px | - -#### 实现示例 -```tsx -export const Button: React.FC = ({ - variant = 'primary', - size = 'md', - disabled = false, - loading = false, - fullWidth = false, - children, - onClick, - className, -}) => { - const variantClasses = { - primary: 'bg-brand-primary text-white hover:bg-brand-primary-dark', - secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200 border border-gray-200', - outline: 'bg-transparent text-gray-800 border border-gray-300 hover:bg-gray-50', - ghost: 'bg-transparent text-gray-800 hover:bg-gray-100', - }; - - const sizeClasses = { - xs: 'h-7 px-3 text-xs', - sm: 'h-8 px-4 text-sm', - md: 'h-10 px-5 text-base', - lg: 'h-12 px-6 text-lg', - }; - - return ( - - ); -}; -``` - ---- - -### 2.2 Input 组件 - -#### 属性定义 -```tsx -interface InputProps { - type?: 'text' | 'email' | 'password' | 'number' | 'search'; - placeholder?: string; - value?: string; - onChange?: (value: string) => void; - disabled?: boolean; - error?: string; - icon?: React.ReactNode; - size?: 'sm' | 'md' | 'lg'; - className?: string; -} -``` - -#### 样式规范 -- **背景**:`#F9FAFB` -- **边框**:`1px solid #D1D5DB` -- **Focus**:边框 `#D91B5C`,阴影 `0 0 0 3px rgba(217, 27, 92, 0.1)` -- **圆角**:6px -- **高度**:40px(md) -- **内边距**:10px 12px -- **字体**:14px,颜色 `#374151` -- **Placeholder**:14px,颜色 `#9CA3AF` -- **Error 状态**:边框变为红色 `#EF4444`,下方显示错误文本 - -#### 实现示例 -```tsx -export const Input: React.FC = ({ - type = 'text', - placeholder, - value, - onChange, - disabled, - error, - icon, - size = 'md', - className, -}) => { - return ( -
- onChange?.(e.target.value)} - placeholder={placeholder} - disabled={disabled} - className={clsx( - 'w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded-md', - 'focus:outline-none focus:border-brand-primary focus:ring-2 focus:ring-brand-light', - 'placeholder-gray-400 text-gray-800', - 'transition-all duration-200', - error && 'border-error focus:border-error', - disabled && 'opacity-50 cursor-not-allowed', - icon && 'pl-9', - className, - )} - /> - {icon && {icon}} - {error && ( - {error} - )} -
- ); -}; -``` - ---- - -### 2.3 Card 组件 - -#### 属性定义 -```tsx -interface CardProps { - children: React.ReactNode; - variant?: 'default' | 'elevated' | 'outlined'; - padding?: 'none' | 'sm' | 'md' | 'lg'; - className?: string; - onClick?: () => void; -} -``` - -#### 样式规范 -- **背景**:白色 `#FFFFFF` -- **边框**:`1px solid #E5E7EB`(outlined 变体) -- **圆角**:12px -- **阴影**: - - default: `0 1px 3px rgba(0, 0, 0, 0.1)` - - elevated: `0 4px 12px rgba(0, 0, 0, 0.15)` - - outlined: 无 -- **过渡**:hover 时阴影升高 - ---- - -### 2.4 Badge 组件 - -#### 属性定义 -```tsx -interface BadgeProps { - children: React.ReactNode; - variant?: 'primary' | 'success' | 'warning' | 'error' | 'info'; - size?: 'sm' | 'md'; - icon?: React.ReactNode; -} -``` - -#### 样式规范 -``` -variant: primary - 背景: rgba(217, 27, 92, 0.1) - 文字: #D91B5C - -variant: success - 背景: rgba(16, 185, 129, 0.1) - 文字: #10B981 -``` - ---- - -### 2.5 Tag 组件 - -#### 属性定义 -```tsx -interface TagProps { - label: string; - color?: string; - onRemove?: () => void; - interactive?: boolean; -} -``` - -#### 样式规范 -- **背景**:`rgba(217, 27, 92, 0.1)`(默认) -- **文字**:`#D91B5C` -- **内边距**:4px 8px -- **圆角**:12px -- **字体**:11px 粗体 -- **可删除**:右侧显示 `×` 按钮 - ---- - -### 2.6 Modal 组件 - -#### 属性定义 -```tsx -interface ModalProps { - isOpen: boolean; - title?: string; - children: React.ReactNode; - onClose: () => void; - size?: 'sm' | 'md' | 'lg'; - showCloseButton?: boolean; -} -``` - -#### 样式规范 -- **背景 Overlay**:`rgba(0, 0, 0, 0.5)` -- **对话框**:白色卡片,圆角 12px,阴影 xl -- **尺寸**: - - sm: 400px - - md: 600px - - lg: 800px -- **动画**:`scale(0.9, 0) → scale(1, 1)`,200ms ease-out - ---- - -### 2.7 Spinner 组件 - -#### 属性定义 -```tsx -interface SpinnerProps { - size?: 'sm' | 'md' | 'lg'; - color?: string; -} -``` - -#### 样式规范 -- **尺寸**:16px (sm), 24px (md), 32px (lg) -- **颜色**:`#D91B5C`(默认) -- **动画**:旋转 360°,2s 无限循环 - ---- - -## 3. 容器组件(Layout Components) - -### 3.1 Layout 组件(主框架) - -```tsx -interface LayoutProps { - children: React.ReactNode; - showSidebar?: boolean; -} - -export const Layout: React.FC = ({ children, showSidebar = true }) => { - return ( -
- {showSidebar && } -
- {children} -
-
- ); -}; -``` - -### 3.2 Sidebar 组件 - -```tsx -interface SidebarProps { - activeTab?: string; - onTabChange?: (tab: string) => void; -} - -export const Sidebar: React.FC = ({ activeTab, onTabChange }) => { - // 导航项列表 - const navItems = [ - { id: 'overview', label: '总览', icon: '◆' }, - { id: 'targets', label: '攻略对象', icon: '◆' }, - { id: 'editor', label: '对话编辑器', icon: '◆' }, - { id: 'llm', label: 'LLM 配置', icon: '◆' }, - { id: 'settings', label: '设置', icon: '◆' }, - ]; - - return ( - - ); -}; -``` - -### 3.3 Header 组件 - -```tsx -interface HeaderProps { - title: string; - subtitle?: string; - actions?: React.ReactNode; -} - -export const Header: React.FC = ({ title, subtitle, actions }) => { - return ( -
-
-
-

{title}

- {subtitle &&

{subtitle}

} -
- {actions &&
{actions}
} -
-
- ); -}; -``` - ---- - -## 4. 业务组件(Feature Components) - -### 4.1 MessageBubble 组件 - -```tsx -interface MessageBubbleProps { - content: string; - role: 'user' | 'partner'; - timestamp?: Date; - isKeyPoint?: boolean; - sentimentImpact?: number; - onEdit?: () => void; -} - -export const MessageBubble: React.FC = ({ - content, - role, - timestamp, - isKeyPoint, - sentimentImpact, - onEdit, -}) => { - const isUser = role === 'user'; - - return ( -
-
-

{content}

- {timestamp && ( - - {timestamp.toLocaleTimeString()} - - )} -
- - {isKeyPoint && } - {sentimentImpact && ( - 0 ? 'text-success' : 'text-error')}> - {sentimentImpact > 0 ? '+' : ''}{sentimentImpact} - - )} -
- ); -}; -``` - -### 4.2 SuggestionCard 组件 - -```tsx -interface SuggestionCardProps { - suggestion: string; - tags: string[]; - expectedImpact?: number; - onSelect?: () => void; - onDismiss?: () => void; -} - -export const SuggestionCard: React.FC = ({ - suggestion, - tags, - expectedImpact, - onSelect, - onDismiss, -}) => { - return ( - -

{suggestion}

-
-
- {tags.map(tag => ( - {tag} - ))} -
- {expectedImpact && ( - ❤️ +{expectedImpact} - )} -
-
- - -
-
- ); -}; -``` - -### 4.3 StatCard 组件 - -```tsx -interface StatCardProps { - label: string; - value: number | string; - icon?: React.ReactNode; - trend?: 'up' | 'down'; -} - -export const StatCard: React.FC = ({ - label, - value, - icon, - trend, -}) => { - return ( - -
-
-

{label}

-

{value}

-
- {icon && {icon}} -
- {trend && ( -

- {trend === 'up' ? '↑' : '↓'} 较上周 -

- )} -
- ); -}; -``` - -### 4.4 ConversationCard 组件 - -```tsx -interface ConversationCardProps { - title: string; - description: string; - partners: string[]; - lastEdited: Date; - onClick?: () => void; -} - -export const ConversationCard: React.FC = ({ - title, - description, - partners, - lastEdited, - onClick, -}) => { - return ( - -

{title}

-

{description}

- -
-
- {partners.map(partner => ( - - ))} -
- - {formatRelativeTime(lastEdited)} - -
-
- ); -}; -``` - ---- - -## 5. 组件状态管理模式 - -### 5.1 使用 Zustand -```tsx -import create from 'zustand'; - -interface UIStore { - sidebarOpen: boolean; - toggleSidebar: () => void; - - selectedConversation: string | null; - setSelectedConversation: (id: string | null) => void; - - showModal: boolean; - setShowModal: (show: boolean) => void; -} - -export const useUIStore = create((set) => ({ - sidebarOpen: true, - toggleSidebar: () => set(state => ({ sidebarOpen: !state.sidebarOpen })), - - selectedConversation: null, - setSelectedConversation: (id) => set({ selectedConversation: id }), - - showModal: false, - setShowModal: (show) => set({ showModal: show }), -})); -``` - ---- - -## 6. 动画和过渡库 - -### 6.1 使用 Framer Motion -```tsx -import { motion, AnimatePresence } from 'framer-motion'; - -/* 淡入淡出动画 */ -export const fadeInOut = { - initial: { opacity: 0 }, - animate: { opacity: 1 }, - exit: { opacity: 0 }, - transition: { duration: 0.2 }, -}; - -/* 从下滑入 */ -export const slideUp = { - initial: { y: 20, opacity: 0 }, - animate: { y: 0, opacity: 1 }, - exit: { y: 20, opacity: 0 }, - transition: { duration: 0.3, ease: 'easeOut' }, -}; - -/* 从右滑入 */ -export const slideInRight = { - initial: { x: 300, opacity: 0 }, - animate: { x: 0, opacity: 1 }, - exit: { x: 300, opacity: 0 }, - transition: { duration: 0.3, ease: 'easeOut' }, -}; - -/* 缩放 + 淡入 */ -export const scaleIn = { - initial: { scale: 0.9, opacity: 0 }, - animate: { scale: 1, opacity: 1 }, - exit: { scale: 0.9, opacity: 0 }, - transition: { duration: 0.2, ease: 'easeOut' }, -}; - -/* Stagger 容器 */ -export const container = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { - staggerChildren: 0.05, - }, - }, -}; - -/* Stagger 子项 */ -export const item = { - hidden: { opacity: 0, y: 20 }, - show: { - opacity: 1, - y: 0, - transition: { duration: 0.3 }, - }, -}; -``` - ---- - -## 7. 类型定义(TypeScript) - -### 7.1 共享类型 -```typescript -/* 通用类型 */ -export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; -export type Variant = 'primary' | 'secondary' | 'outline' | 'ghost'; -export type Status = 'idle' | 'loading' | 'success' | 'error'; - -/* 用户相关 */ -export interface User { - id: string; - name: string; - avatar?: string; -} - -/* 对话相关 */ -export interface Conversation { - id: string; - title: string; - description?: string; - partnerId: string; - messages: Message[]; - createdAt: Date; - updatedAt: Date; -} - -export interface Message { - id: string; - role: 'user' | 'partner'; - content: string; - timestamp: Date; - isKeyPoint?: boolean; - sentimentImpact?: number; -} - -/* 分析相关 */ -export interface Analysis { - id: string; - type: 'positive' | 'neutral' | 'improvement' | 'turning_point'; - text: string; - messageId?: string; -} - -export interface SentimentCurve { - points: number[]; - startValue: number; - endValue: number; - maxValue: number; - minValue: number; -} -``` - ---- - -## 8. 主题切换(深色/浅色模式) - -### 8.1 主题上下文 -```tsx -import React, { createContext, useContext, useState } from 'react'; - -type Theme = 'light' | 'dark'; - -interface ThemeContextType { - theme: Theme; - toggleTheme: () => void; -} - -const ThemeContext = createContext(undefined); - -export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [theme, setTheme] = useState('dark'); - - const toggleTheme = () => { - setTheme(t => t === 'light' ? 'dark' : 'light'); - // 同时更新 HTML 属性和 localStorage - document.documentElement.setAttribute('data-theme', theme); - localStorage.setItem('theme', theme); - }; - - return ( - - {children} - - ); -}; - -export const useTheme = () => { - const context = useContext(ThemeContext); - if (!context) throw new Error('useTheme must be used within ThemeProvider'); - return context; -}; -``` - -### 8.2 Tailwind 深色模式配置 -```js -// tailwind.config.js -module.exports = { - darkMode: 'class', - theme: { - colors: { - /* ... */ - }, - }, -}; -``` - ---- - -## 9. 测试覆盖(Jest + React Testing Library) - -### 9.1 按钮组件测试示例 -```typescript -import { render, screen, fireEvent } from '@testing-library/react'; -import { Button } from './Button'; - -describe('Button Component', () => { - it('renders button with correct text', () => { - render(); - expect(screen.getByText('Click me')).toBeInTheDocument(); - }); - - it('applies primary variant styles', () => { - render(); - const button = screen.getByText('Submit'); - expect(button).toHaveClass('bg-brand-primary', 'text-white'); - }); - - it('handles click events', () => { - const handleClick = jest.fn(); - render(); - - fireEvent.click(screen.getByText('Click')); - expect(handleClick).toHaveBeenCalled(); - }); - - it('disables button when disabled prop is true', () => { - render(); - expect(screen.getByText('Disabled')).toBeDisabled(); - }); -}); -``` - ---- - -## 10. 性能优化 - -### 10.1 使用 React.memo 避免不必要重渲染 -```tsx -export const MessageBubble = React.memo((props: MessageBubbleProps) => { - // ... -}); -``` - -### 10.2 使用 useCallback 缓存函数 -```tsx -const handleSave = useCallback(() => { - saveConversation(conversationId); -}, [conversationId]); -``` - -### 10.3 列表虚拟化(大数据量) -```tsx -import { FixedSizeList as List } from 'react-window'; - -const MessageList = ({ messages }: { messages: Message[] }) => { - const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => ( -
- -
- ); - - return ( - - {Row} - - ); -}; -``` - ---- - -## 11. 无障碍(Accessibility) - -### 11.1 ARIA 属性 -```tsx - - - -``` - -### 11.2 键盘导航 -```tsx -const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onClick?.(); - } - if (e.key === 'Escape') { - onClose?.(); - } -}; -``` - ---- - -## 12. 文档与示例(Storybook) - -### 12.1 按钮 Story -```tsx -import { Button } from './Button'; - -export default { - title: 'Components/Button', - component: Button, -}; - -export const Primary = () => ; -export const Secondary = () => ; -export const Disabled = () => ; -export const Loading = () => ; -``` - ---- - -## 13. 组件清单 - -| 组件名 | 类型 | 用途 | 依赖 | -|-------|------|------|------| -| Button | 基础 | 通用按钮 | - | -| Input | 基础 | 文本输入 | - | -| Card | 基础 | 卡片容器 | - | -| Badge | 基础 | 徽章标签 | - | -| Tag | 基础 | 可删除标签 | - | -| Modal | 基础 | 模态对话框 | Framer Motion | -| Spinner | 基础 | 加载指示器 | Framer Motion | -| Layout | 容器 | 主框架 | Sidebar, Header | -| Sidebar | 容器 | 侧边栏导航 | Button | -| Header | 容器 | 页面头部 | Button | -| MessageBubble | 业务 | 聊天气泡 | Card | -| SuggestionCard | 业务 | 建议卡片 | Card, Badge, Button | -| StatCard | 业务 | 统计卡片 | Card | -| ConversationCard | 业务 | 对话卡片 | Card, Tag | - ---- - -## 14. 版本管理与更新流程 - -### 14.1 Changelog 示例 -``` -## v1.0.0 (2025-11-15) - -### 新增 -- Button 组件:支持 4 种变体、4 种尺寸 -- Input 组件:支持错误状态和图标 -- Modal 组件:完整的模态对话框实现 - -### 改进 -- 优化 MessageBubble 动画性能 -- 改进 Tag 组件可访问性 - -### 修复 -- 修复 Button loading 状态在移动端的显示问题 -``` - ---- - -**文档版本**:v1.0 -**最后更新**:2025-11-12 -**维护者**:前端架构团队 -**设计工具**:Figma (组件库链接) -**代码仓库**:`/src/components` -**Storybook**:http://localhost:6006 - diff --git a/desktop/src/main.js b/desktop/src/main.js index 7d323ce..71a9756 100644 --- a/desktop/src/main.js +++ b/desktop/src/main.js @@ -18,25 +18,38 @@ function createWindow() { nodeIntegration: false, contextIsolation: true, enableRemoteModule: false, - preload: path.join(__dirname, 'preload.js') + preload: path.join(__dirname, 'preload.js'), + // 禁用开发者工具快捷键(可选,如果需要可以注释掉) + // devTools: false }, - titleBarStyle: 'hidden', // 隐藏标题栏 + titleBarStyle: 'hidden', // macOS 隐藏标题栏 show: false, // 先不显示,准备好后再显示 title: 'LiveGalGame Desktop', - // 无边框窗口 - frame: false - }); - - // 加载index.html - mainWindow.loadFile(path.join(__dirname, 'renderer/index.html')); + // 无边框窗口,看起来更像客户端应用 + frame: false, + // 确保窗口看起来像原生应用 + backgroundColor: '#f8f6f7', // 浅色背景色,避免加载时闪烁 + // 禁用菜单栏(可选) + autoHideMenuBar: true + }); + + // 加载React应用 + if (process.env.NODE_ENV === 'development') { + // 开发环境:加载Vite开发服务器 + mainWindow.loadURL('http://localhost:5173'); + } else { + // 生产环境:加载构建后的文件 + mainWindow.loadFile(path.join(__dirname, '../dist/renderer/index.html')); + } // 窗口准备就绪后显示 mainWindow.once('ready-to-show', () => { mainWindow.show(); - // 开发环境自动打开开发者工具 - if (process.env.NODE_ENV === 'development') { - mainWindow.webContents.openDevTools(); - } + // 开发环境不自动打开开发者工具,保持客户端外观 + // 如需调试,可以使用快捷键 Cmd+Shift+I (Mac) 或 Ctrl+Shift+I (Windows/Linux) + // if (process.env.NODE_ENV === 'development') { + // mainWindow.webContents.openDevTools(); + // } }); // 窗口关闭事件 @@ -464,8 +477,14 @@ function createHUDWindow() { // 确保窗口可以调整大小(显式设置) hudWindow.setResizable(true); - // 加载HUD页面 - hudWindow.loadFile(path.join(__dirname, 'renderer/hud.html')); + // 加载HUD页面 - 区分开发和生产环境 + if (process.env.NODE_ENV === 'development') { + // 开发环境:从Vite服务器加载(使用不同端口或路由) + hudWindow.loadURL('http://localhost:5173/hud.html'); + } else { + // 生产环境:从构建后的文件加载 + hudWindow.loadFile(path.join(__dirname, '../dist/renderer/hud.html')); + } // 页面加载完成后再显示 hudWindow.once('ready-to-show', () => { diff --git a/desktop/src/renderer/App.jsx b/desktop/src/renderer/App.jsx new file mode 100644 index 0000000..1d64fec --- /dev/null +++ b/desktop/src/renderer/App.jsx @@ -0,0 +1,24 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; +import Layout from './components/Layout'; +import Overview from './pages/Overview'; +import Characters from './pages/Characters'; +import ConversationEditor from './pages/ConversationEditor'; +import Settings from './pages/Settings'; + +function App() { + console.log('App component rendering'); + return ( + + + } /> + } /> + } /> + } /> + } /> + + + ); +} + +export default App; + diff --git a/desktop/src/renderer/characters.html b/desktop/src/renderer/characters.html deleted file mode 100644 index b62d367..0000000 --- a/desktop/src/renderer/characters.html +++ /dev/null @@ -1,812 +0,0 @@ - - - - - - LiveGalGame - 攻略对象 - - - - - - - - - -
- - - - -
-
- -
-
-

攻略对象

-
-

管理与各个角色的关系和档案

-
- - -
-
-
- 总计攻略对象 - groups -
-
-
-
- -
-
-
- 活跃对话 -
- - -
-
- chat -
-
-
-
- -
-
- 平均好感度 - favorite -
-
-
-
-
- - -
- -
-
-

加载中...

-
-
-
-
-
- - - - - - - diff --git a/desktop/src/renderer/components/Layout.jsx b/desktop/src/renderer/components/Layout.jsx new file mode 100644 index 0000000..d05dc0b --- /dev/null +++ b/desktop/src/renderer/components/Layout.jsx @@ -0,0 +1,112 @@ +import { Link, useLocation } from 'react-router-dom'; + +function Layout({ children }) { + const location = useLocation(); + + const isActive = (path) => { + if (path === '/') { + return location.pathname === '/'; + } + return location.pathname.startsWith(path); + }; + + return ( +
+ {/* 左侧导航栏 */} + + + {/* 主内容区域 */} +
+ {children} +
+
+ ); +} + +export default Layout; + diff --git a/desktop/src/renderer/conversation-editor.html b/desktop/src/renderer/conversation-editor.html deleted file mode 100644 index d2a35c0..0000000 --- a/desktop/src/renderer/conversation-editor.html +++ /dev/null @@ -1,1007 +0,0 @@ - - - - - - LiveGalGame - 历史对话 - - - - - - - - - -
- - - - -
- -
-
-

历史对话

-

点击左侧对话列表查看详情

-
-
- - - -
- - - -
- - - - - diff --git a/desktop/src/renderer/hud.css b/desktop/src/renderer/hud.css new file mode 100644 index 0000000..4ce3814 --- /dev/null +++ b/desktop/src/renderer/hud.css @@ -0,0 +1,246 @@ +:root { + font-family: 'Noto Sans SC', 'Segoe UI', system-ui, sans-serif; + color: #1f1d2b; +} + +body { + margin: 0; + background: transparent; +} + +#hud-root { + width: 100vw; + height: 100vh; +} + +.hud-body { + width: 100%; + height: 100%; + background: transparent; +} + +.hud-container { + width: 100%; + height: 100%; + padding: 18px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 16px; + background: rgba(255, 255, 255, 0.78); + border-radius: 20px; + border: 1px solid rgba(255, 255, 255, 0.5); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35), inset 0 0 40px rgba(255, 255, 255, 0.35); + backdrop-filter: blur(26px); +} + +.hud-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.hud-drag-zone { + display: inline-flex; + align-items: center; + gap: 8px; + cursor: grab; + user-select: none; +} + +.hud-drag-zone.hud-dragging { + cursor: grabbing; +} + +.hud-title { + font-weight: 600; + font-size: 15px; + color: #c51662; +} + +.hud-controls { + display: flex; + gap: 10px; +} + +.control-btn { + width: 32px; + height: 32px; + border-radius: 50%; + border: none; + background: rgba(197, 22, 98, 0.12); + color: #c51662; + font-size: 20px; + cursor: pointer; + transition: transform 0.2s ease, background 0.2s ease; +} + +.control-btn:hover { + background: rgba(197, 22, 98, 0.25); + transform: scale(1.08); +} + +.status-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: #22c55e; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.4; + } + 100% { + opacity: 1; + } +} + +.hud-section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.section-label { + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #974e6e; +} + +.transcript-area { + min-height: 140px; + max-height: 170px; + padding: 10px; + border-radius: 14px; + background: rgba(15, 23, 42, 0.04); + border: 1px solid rgba(15, 23, 42, 0.1); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 6px; +} + +.hud-status { + text-align: center; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 12px; + gap: 6px; + color: rgba(15, 23, 42, 0.85); +} + +.hud-status-text { + font-size: 13px; +} + +.hud-error { + color: #c51662; +} + +.hud-spinner { + width: 26px; + height: 26px; + border-radius: 50%; + border: 3px solid rgba(255, 255, 255, 0.5); + border-bottom-color: #c51662; + animation: spinner 0.9s linear infinite; + box-shadow: 0 0 12px rgba(197, 22, 98, 0.35); +} + +@keyframes spinner { + to { + transform: rotate(360deg); + } +} + +.message-item { + display: flex; +} + +.message-other { + justify-content: flex-start; +} + +.message-user { + justify-content: flex-end; +} + +.message-bubble { + max-width: 78%; + padding: 10px 14px; + border-radius: 16px; + font-size: 13px; + line-height: 1.5; + word-break: break-word; + background: rgba(255, 255, 255, 0.9); + color: #111827; + box-shadow: 0 6px 16px rgba(17, 24, 39, 0.08); +} + +.message-user .message-bubble { + background: rgba(197, 22, 98, 0.12); + color: #c51662; +} + +.suggestions-grid { + display: flex; + flex-direction: column; + gap: 12px; +} + +.suggestion-card { + padding: 12px 14px; + border-radius: 16px; + background: linear-gradient(135deg, rgba(197, 22, 98, 0.1), rgba(142, 36, 170, 0.08)); + border: 1px solid rgba(197, 22, 98, 0.3); + box-shadow: inset 0 0 12px rgba(255, 255, 255, 0.3); + transition: transform 0.2s ease; +} + +.suggestion-card:hover { + transform: translateY(-2px); +} + +.suggestion-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 6px; + color: #c51662; + font-size: 13px; + font-weight: 600; +} + +.suggestion-body { + font-size: 13px; + line-height: 1.5; + color: rgba(15, 23, 42, 0.9); + margin: 0; +} + +.suggestion-meta { + display: flex; + gap: 6px; + margin-top: 10px; + flex-wrap: wrap; +} + +.suggestion-badge { + padding: 2px 8px; + font-size: 10px; + font-weight: 600; + border-radius: 999px; + background: rgba(255, 255, 255, 0.5); + color: #5b21b6; +} + diff --git a/desktop/src/renderer/hud.html b/desktop/src/renderer/hud.html index 567cbfc..4dd257c 100644 --- a/desktop/src/renderer/hud.html +++ b/desktop/src/renderer/hud.html @@ -4,426 +4,13 @@ LiveGalGame HUD - - + + + + - -
- -
-
- -
-
- -
-
- - -
-
- -
-
-

加载中...

-
-
-
- - -
-
-
-
- 提议具体地点 - ❤️ +15 -
-

"我知道附近有个很棒的公园,樱花特别美,要不要去那里?"

-
- 主动 - 体贴 -
-
- -
-
- 表达期待 - ❤️ +10 -
-

"太好了!我一直想和你一起去散步呢。"

-
- 情感 - 真诚 -
-
-
-
-
- - + +
+ diff --git a/desktop/src/renderer/hud.jsx b/desktop/src/renderer/hud.jsx new file mode 100644 index 0000000..63eadcf --- /dev/null +++ b/desktop/src/renderer/hud.jsx @@ -0,0 +1,208 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import ReactDOM from 'react-dom/client'; +import './hud.css'; + +const HUD_SUGGESTIONS = [ + { + title: '提议具体地点', + body: '"我知道附近有个很棒的公园,樱花特别美,要不要去那里?"', + badges: ['主动', '体贴'] + }, + { + title: '表达期待', + body: '"太好了!我一直想和你一起去散步呢。"', + badges: ['情感', '真诚'] + } +]; + +const getPointerCoords = (event) => { + const x = event.screenX !== undefined && event.screenX !== null ? event.screenX : event.clientX; + const y = event.screenY !== undefined && event.screenY !== null ? event.screenY : event.clientY; + return { x, y }; +}; + +function Hud() { + const [messages, setMessages] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [isDragging, setIsDragging] = useState(false); + const transcriptRef = useRef(null); + const dragStateRef = useRef({ dragging: false, startX: 0, startY: 0 }); + + const loadMessages = useCallback(async () => { + setLoading(true); + setError(''); + try { + const api = window.electronAPI; + if (!api || !api.getRecentConversations || !api.getMessagesByConversation) { + throw new Error('数据库API不可用'); + } + + const conversations = await api.getRecentConversations(1); + if (!conversations?.length) { + setMessages([]); + return; + } + + const conversationId = conversations[0].id; + const fetchedMessages = await api.getMessagesByConversation(conversationId); + setMessages(fetchedMessages || []); + } catch (err) { + console.error('加载对话失败:', err); + setError(err instanceof Error ? err.message : '加载失败'); + setMessages([]); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + setTimeout(() => loadMessages(), 0); + }, [loadMessages]); + + useEffect(() => { + if (transcriptRef.current) { + transcriptRef.current.scrollTop = transcriptRef.current.scrollHeight; + } + }, [messages]); + + useEffect(() => { + const handleMouseMove = (event) => { + const api = window.electronAPI; + if (!dragStateRef.current.dragging || !api?.updateHUDDrag) return; + const pos = getPointerCoords(event); + api.updateHUDDrag(pos); + }; + + const handleMouseUp = () => { + if (!dragStateRef.current.dragging) return; + dragStateRef.current.dragging = false; + setIsDragging(false); + window.electronAPI?.endHUDDrag?.(); + }; + + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseup', handleMouseUp); + window.addEventListener('mouseleave', handleMouseUp); + + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseup', handleMouseUp); + window.removeEventListener('mouseleave', handleMouseUp); + }; + }, []); + + const handleDragStart = (event) => { + const api = window.electronAPI; + if (!api?.startHUDDrag) return; + const pos = getPointerCoords(event); + dragStateRef.current = { dragging: true, startX: pos.x, startY: pos.y }; + setIsDragging(true); + api.startHUDDrag(pos); + event.preventDefault(); + event.stopPropagation(); + }; + + const handleClose = () => { + if (window.electronAPI?.closeHUD) { + window.electronAPI.closeHUD(); + } + }; + + const renderTranscriptContent = () => { + if (loading) { + return ( +
+
+ ); + } + + if (error) { + return ( +
+

加载失败:{error}

+
+ ); + } + + if (!messages.length) { + return ( +
+

该对话还没有消息

+
+ ); + } + + return messages.map((msg, index) => { + const isUser = msg.sender === 'user'; + const key = msg.id ?? `${msg.sender}-${msg.timestamp ?? index}`; + return ( +
+
{msg.content || msg.text || ''}
+
+ ); + }); + }; + + return ( +
+
+
+ + 心情助手 +
+
+ +
+
+ +
+
最近互动
+
+ {renderTranscriptContent()} +
+
+ +
+
AI 建议
+
+ {HUD_SUGGESTIONS.map((suggestion) => ( +
+
+ {suggestion.title} +
+

{suggestion.body}

+
+ {suggestion.badges.map((badge) => ( + + {badge} + + ))} +
+
+ ))} +
+
+
+ ); +} + +const hudRoot = document.getElementById('hud-root'); +if (hudRoot) { + ReactDOM.createRoot(hudRoot).render( + + + + ); +} else { + console.error('HUD root element not found'); +} + diff --git a/desktop/src/renderer/index.css b/desktop/src/renderer/index.css new file mode 100644 index 0000000..0cfa65c --- /dev/null +++ b/desktop/src/renderer/index.css @@ -0,0 +1,40 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --primary: #c51662; + --background-light: #f8f6f7; + --background-dark: #211118; + --text-light: #1b0e14; + --text-dark: #f8f6f7; + --text-muted-light: #974e6e; + --text-muted-dark: #a88fa0; + --surface-light: #ffffff; + --surface-dark: #2a161f; + --border-light: #f3e7ec; + --border-dark: #402634; + --primary-subtle-light: #f3e7ec; + --primary-subtle-dark: #402634; + --success: #22c55e; + --warning: #f59e0b; + --error: #ef4444; + } + + body { + font-family: 'Plus Jakarta Sans', 'Noto Sans SC', sans-serif; + margin: 0; + padding: 0; + } + + .material-symbols-outlined { + font-variation-settings: + 'FILL' 0, + 'wght' 400, + 'GRAD' 0, + 'opsz' 24; + font-size: 24px; + } +} + diff --git a/desktop/src/renderer/index.html b/desktop/src/renderer/index.html index e2094cd..93f31b8 100644 --- a/desktop/src/renderer/index.html +++ b/desktop/src/renderer/index.html @@ -1,286 +1,17 @@ - + - LiveGalGame - 总览 - + LiveGalGame Desktop - - - -
- - - - -
-
- -
-

欢迎回来!

-

这是您的对话项目快照。

-
- - -
- -
-
-

加载中...

-
-
- - -
-
-

最近对话

-
- - -
-
-

"我"与攻略对象的聊天记录摘要。

-
- - -
- -
-
-

加载中...

-
-
-
-
-
- - - + +
+ diff --git a/desktop/src/renderer/main.jsx b/desktop/src/renderer/main.jsx new file mode 100644 index 0000000..54b4a0e --- /dev/null +++ b/desktop/src/renderer/main.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + + + +); + diff --git a/desktop/src/renderer/pages/Characters.jsx b/desktop/src/renderer/pages/Characters.jsx new file mode 100644 index 0000000..fad0d18 --- /dev/null +++ b/desktop/src/renderer/pages/Characters.jsx @@ -0,0 +1,581 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; + +function Characters() { + const [statistics, setStatistics] = useState(null); + const [characters, setCharacters] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedCharacter, setSelectedCharacter] = useState(null); + const [showDetailModal, setShowDetailModal] = useState(false); + const [characterDetails, setCharacterDetails] = useState(null); + const navigate = useNavigate(); + + useEffect(() => { + loadStatistics(); + loadCharacters(); + }, []); + + const loadStatistics = async () => { + try { + if (window.electronAPI?.getCharacterPageStatistics) { + const stats = await window.electronAPI.getCharacterPageStatistics(); + setStatistics(stats); + } + } catch (error) { + console.error('Failed to load statistics:', error); + } + }; + + const loadCharacters = async () => { + try { + setLoading(true); + if (window.electronAPI?.getAllCharacters) { + const chars = await window.electronAPI.getAllCharacters(); + setCharacters(chars); + } + } catch (error) { + console.error('Failed to load characters:', error); + } finally { + setLoading(false); + } + }; + + const viewCharacterHistory = (characterId) => { + navigate(`/conversations?character=${characterId}`); + }; + + const viewCharacterDetail = async (characterId) => { + try { + setSelectedCharacter(characterId); + setShowDetailModal(true); + setCharacterDetails(null); + + if (window.electronAPI?.getCharacterDetails) { + const details = await window.electronAPI.getCharacterDetails(characterId); + setCharacterDetails(details); + } + } catch (error) { + console.error('Failed to load character details:', error); + } + }; + + const closeCharacterDetailModal = () => { + setShowDetailModal(false); + setSelectedCharacter(null); + setCharacterDetails(null); + }; + + const getAvatarGradient = (color) => { + if (color?.includes('ff6b6b')) return 'bg-gradient-to-br from-[#ff6b6b] to-[#ff8e8e]'; + if (color?.includes('4ecdc4')) return 'bg-gradient-to-br from-[#4ecdc4] to-[#6ee5dd]'; + if (color?.includes('ffe66d')) return 'bg-gradient-to-br from-[#ffe66d] to-[#fff099]'; + return 'bg-gradient-to-br from-primary to-[#8e24aa]'; + }; + + return ( +
+ {/* 页面标题 */} +
+
+

+ 攻略对象 +

+
+

+ 管理与各个角色的关系和档案 +

+
+ + {/* 统计卡片 */} +
+
+
+ 总计攻略对象 + groups +
+
+ {statistics?.characterCount ?? '-'} +
+
+ +
+
+
+ 活跃对话 +
+ +
+ 显示两天内(48小时)创建的新对话数量 +
+
+
+
+ chat +
+
+ {statistics?.activeConversationCount ?? '-'} +
+
+ +
+
+ 平均好感度 + favorite +
+
+ {statistics?.avgAffinity ?? '-'} +
+
+
+ + {/* 攻略对象列表 */} +
+ {loading ? ( +
+
+

加载中...

+
+ ) : characters.length === 0 ? ( +
+

还没有角色数据

+

请在数据库中添加角色数据

+
+ ) : ( + characters.map((character) => { + const firstLetter = character.name.charAt(0); + const avatarGradient = getAvatarGradient(character.avatar_color); + + return ( +
+
+
+ {firstLetter} +
+
+

{character.name}

+

+ {character.relationship_label} +

+
+
+ +
+
+ 好感度 + {character.affinity}% +
+
+
+
+
+ +
+
+ 角色ID + {character.id} +
+
+ 创建时间 + {new Date(character.created_at).toLocaleDateString('zh-CN')} +
+
+ +
+

关键词

+
+ {character.tags?.map((tag, index) => ( + + {tag} + + ))} +
+
+ +
+ + +
+
+ ); + }) + )} +
+ + {/* 角色详情弹窗 */} + {showDetailModal && ( + { + await loadCharacters(); + await loadStatistics(); + }} + /> + )} +
+ ); +} + +// 角色详情弹窗组件 +function CharacterDetailModal({ characterId, details, onClose, onSaved }) { + const [formData, setFormData] = useState(null); + const [editMode, setEditMode] = useState(false); + const [newTag, setNewTag] = useState(''); + + useEffect(() => { + if (details) { + setFormData(JSON.parse(JSON.stringify(details))); + } + setEditMode(false); + }, [details]); + + if (!details) { + return ( + +
+
+

加载中...

+
+
+ ); + } + + const toggleEditMode = () => { + if (!editMode) { + setFormData(JSON.parse(JSON.stringify(details))); + } + setEditMode(!editMode); + }; + + const updateTopLevelField = (field, value) => { + setFormData((prev) => ({ + ...prev, + [field]: value, + })); + }; + const updateProfileField = (field, value) => { + setFormData((prev) => ({ + ...prev, + profile: { + ...(prev?.profile || {}), + [field]: value, + }, + })); + }; + + const addTag = () => { + if (!newTag.trim()) return; + setFormData((prev) => { + const tags = prev?.profile?.tags ? [...prev.profile.tags] : []; + if (tags.includes(newTag.trim())) return prev; + return { + ...prev, + profile: { + ...(prev?.profile || {}), + tags: [...tags, newTag.trim()], + }, + }; + }); + setNewTag(''); + }; + + const removeTag = (tag) => { + setFormData((prev) => { + const tags = (prev?.profile?.tags || []).filter((t) => t !== tag); + return { + ...prev, + profile: { + ...(prev?.profile || {}), + tags, + }, + }; + }); + }; + + const saveEditedDetails = async () => { + if (!characterId || !formData) return; + try { + const success = await window.electronAPI.saveCharacterDetails(characterId, formData); + if (success) { + alert('保存成功!'); + setEditMode(false); + if (onSaved) onSaved(); + } else { + alert('保存失败,请重试'); + } + } catch (error) { + console.error('Failed to save edited details:', error); + alert('保存失败:' + error.message); + } + }; + + const personalityTags = details.personality_traits?.keywords || []; + const likes = details.likes_dislikes?.likes || []; + const dislikes = details.likes_dislikes?.dislikes || []; + const events = details.important_events || []; + + return ( + +
+
+ + +
+ +
+
+ {personalityTags.map((tag) => ( + + {tag} + + ))} +
+

+ {details.personality_traits?.descriptions?.join(';') || '暂无性格描述'} +

+
+ +
+
+ + +
+
+ +
+ {events.length === 0 &&

暂无数据

} +
+ {events.map((event, idx) => ( +
+
+
{event.title}
+
+ {event.date ? new Date(event.date).toLocaleDateString('zh-CN') : ''} +
+
+ {event.summary &&

{event.summary}

} + {event.affinity_change !== undefined && ( +
+ 好感度{event.affinity_change > 0 ? '+' : ''} + {event.affinity_change} +
+ )} +
+ ))} +
+
+ +
+ {editMode ? ( +
- -
-
-
-
-

LLM 配置

-

管理您的 AI 模型配置,添加新模型并设置默认值。

-
- -
-
-
-
-
-star -
-
-

GPT-4o (OpenAI)

-
-
-

已激活

-
-

gpt-4o

-
-
-
- - - -
-
-
-
-
-model_training -
-
-

Claude 3 Opus (Anthropic)

-
-
-

未激活

-
-

claude-3-opus-20240229

-
-
-
- - - - -
-
-
-
-
-dns -
-
-

本地 Llama 3 (Oobabooga)

-
-
-

连接错误

-
-

本地 Oobabooga

-
-
-
- - - - -
-
-
-add_circle -

尚未配置模型

-

通过添加您的第一个 AI 模型配置开始。连接到 OpenAI、Anthropic 或本地实例。

- -
-
-
-
-

+ + + + +
+ +
+
+

欢迎使用对话编辑器

+

点击左侧对话列表开始编辑

+
+
+ + + +
+ + + +