From 07b7416d252a60fddf65edfac05a758d95b05a32 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 19 Mar 2026 13:09:35 +0800 Subject: [PATCH 1/6] fix: chat for dad mode --- frontend/src/components/dad/DcChat.vue | 27 +++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/dad/DcChat.vue b/frontend/src/components/dad/DcChat.vue index 4356e1c..6102605 100644 --- a/frontend/src/components/dad/DcChat.vue +++ b/frontend/src/components/dad/DcChat.vue @@ -11,7 +11,7 @@ -
+
@@ -80,7 +80,7 @@ @keydown.up.prevent="navigateHistory('up')" @keydown.down.prevent="navigateHistory('down')" @keydown.enter.exact.prevent="onSend" - /> + />
@@ -267,6 +267,17 @@ function focusInput() { }) } +function onConsoleClick(e: MouseEvent) { + if (sending.value || isStreaming.value) return + // Don't interfere with text selection + const sel = window.getSelection() + if (sel && sel.toString().length > 0) return + // Don't steal focus from interactive elements + const target = e.target as HTMLElement + if (target.closest('a, button, textarea, input')) return + focusInput() +} + function setInputValue(value: string) { input.value = value nextTick(() => { @@ -514,6 +525,8 @@ async function onSend() { display: flex; flex-direction: column; gap: 18px; + user-select: text; + -webkit-user-select: text; } .dc-log-entry { @@ -626,11 +639,11 @@ async function onSend() { } .dc-terminal-editor { - position: fixed; - top: -9999px; - left: -9999px; - width: 1px; - height: 1px; + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 1.8em; border: none; background: transparent; color: transparent; From c68f85292d0b9318bf27e53e3836853f9d9ba77b Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 19 Mar 2026 16:36:24 +0800 Subject: [PATCH 2/6] fix: fix pearl position --- backend/internal/model/task.go | 2 +- frontend/src/components/overlay/CarPage.vue | 195 ++++++++++---------- 2 files changed, 97 insertions(+), 100 deletions(-) diff --git a/backend/internal/model/task.go b/backend/internal/model/task.go index ecf809c..36e9a74 100644 --- a/backend/internal/model/task.go +++ b/backend/internal/model/task.go @@ -105,7 +105,7 @@ type AIGeneratedTask struct { ID uint `gorm:"primaryKey"` CoupleKey string `gorm:"type:varchar(80);uniqueIndex:idx_couple_date_type" json:"couple_key"` Date string `gorm:"type:varchar(10);uniqueIndex:idx_couple_date_type" json:"date"` - Type string `gorm:"type:varchar(20);default:'task';uniqueIndex:idx_couple_date_type" json:"type"` + Type string `gorm:"type:varchar(50);default:'task';uniqueIndex:idx_couple_date_type" json:"type"` AgeStage string `gorm:"type:varchar(20)" json:"age_stage"` Content string `gorm:"column:tasks_json;type:text" json:"content"` // Reusing tasks_json column for content CreatedAt time.Time `json:"created_at"` diff --git a/frontend/src/components/overlay/CarPage.vue b/frontend/src/components/overlay/CarPage.vue index 5039d7b..b5703f5 100644 --- a/frontend/src/components/overlay/CarPage.vue +++ b/frontend/src/components/overlay/CarPage.vue @@ -18,12 +18,8 @@
- +
- -
- -
- -
- - -
+
- -
-
-
- partner avatar - partner frame -
- -
- my avatar - my frame -
+ +
+ +
+ + +
+ + +
+
+
+ partner avatar + partner frame +
+ +
+ my avatar + my frame +
+
- -
-
-
- -
- {{ node.date }} - {{ node.label }} -
+
+
+
+ +
+ {{ node.date }} + {{ node.label }}
+
- -
- overflow box - {{ allPhotos.length }} -
+
+ overflow box + {{ allPhotos.length }}
@@ -1131,9 +1132,10 @@ watch(visible, async (isVisible) => { .car-layout { display: flex; height: 100%; - padding: 5vh 5vw 5vh 20vw; - gap: 4vw; + padding: 5vh 8vw; + gap: 10vw; align-items: center; + justify-content: space-between; } /* ── Photo Wall (Left) ── */ @@ -1147,37 +1149,6 @@ watch(visible, async (isVisible) => { padding-top: 21vh; } -/* ── PearlShell Trigger Zone ── */ -.pearl-shell-trigger-zone { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 936px; - height: 544px; - margin-top: -17px; - margin-left: 0px; - z-index: 1; /* Below photos but above background */ - cursor: pointer; - border-radius: 0; - animation: pulse-glow 3s ease-in-out infinite; -} - -@keyframes pulse-glow { - 0%, 100% { box-shadow: inset 0 0 40px 20px rgba(255, 180, 200, 0.05), inset 0 0 25px 12px rgba(255, 210, 80, 0.05); } - 50% { box-shadow: inset 0 0 80px 40px rgba(255, 180, 200, 0.2), inset 0 0 60px 30px rgba(255, 210, 80, 0.3); } -} - -.pearl-shell-trigger-zone:hover { - animation-name: pulse-glow-hover; - animation-duration: 2s; -} - -@keyframes pulse-glow-hover { - 0%, 100% { box-shadow: inset 0 0 60px 30px rgba(255, 170, 190, 0.2), inset 0 0 40px 20px rgba(255, 210, 80, 0.25); } - 50% { box-shadow: inset 0 0 120px 60px rgba(255, 150, 180, 0.5), inset 0 0 90px 45px rgba(255, 210, 80, 0.55); } -} - .photo-grid { position: relative; z-index: 2; @@ -1244,13 +1215,15 @@ watch(visible, async (isVisible) => { /* ── Right Section ── */ .right-section { + position: absolute; + top: 18.5%; /* 顶部与公告板齐平 */ + left: 69%; /* 起始于右侧木板区域 */ + width: 15%; /* 覆盖木板宽度 */ + height: 65%; /* 覆盖到行李箱位置 */ display: flex; flex-direction: column; align-items: center; - min-width: 400px; - align-self: stretch; - padding-top: 18vh; - margin-right: 8vw; + z-index: 10; } .avatars { @@ -1258,7 +1231,7 @@ watch(visible, async (isVisible) => { align-items: center; gap: 0; cursor: pointer; - margin-bottom: 48px; + margin-bottom: 70%; /* 缩小间距 */ } @keyframes floating-group { @@ -1267,7 +1240,7 @@ watch(visible, async (isVisible) => { } .ecg-link { - width: 120px; + width: 100px; height: 60px; margin: 0 -30px; z-index: 5; @@ -1320,8 +1293,8 @@ watch(visible, async (isVisible) => { .avatar-wrapper { position: relative; - width: 160px; - height: 160px; + width: 120px; + height: 120px; transition: transform 0.2s; } @@ -2367,19 +2340,43 @@ watch(visible, async (isVisible) => { } /* ── PearlShell ── */ +.pearl-shell-trigger-zone { + position: absolute; + top: 19.2%; /* 对齐公告板顶部 */ + left: 16.5%; /* 对齐公告板左侧 */ + width: 49.2%; /* 覆盖公告板宽度 */ + height: 57.9%; /* 覆盖公告板高度 */ + z-index: 1; + cursor: pointer; + border-radius: 4px; + animation: pulse-glow 3s ease-in-out infinite; +} + .pearl-shell-embedded { position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 936px; - height: 544px; - margin-top: -17px; - margin-left: 0px; + top: 19.2%; /* 对齐公告板顶部 */ + left: 16.6%; /* 对齐公告板左侧 */ + width: 49.2%; /* 覆盖公告板宽度 */ + height: 57.9%; border-radius: 2px; overflow: hidden; } +@keyframes pulse-glow { + 0%, 100% { box-shadow: inset 0 0 40px 20px rgba(255, 180, 200, 0.05), inset 0 0 25px 12px rgba(255, 210, 80, 0.05); } + 50% { box-shadow: inset 0 0 80px 40px rgba(255, 180, 200, 0.2), inset 0 0 60px 30px rgba(255, 210, 80, 0.3); } +} + +.pearl-shell-trigger-zone:hover { + animation-name: pulse-glow-hover; + animation-duration: 2s; +} + +@keyframes pulse-glow-hover { + 0%, 100% { box-shadow: inset 0 0 60px 30px rgba(255, 170, 190, 0.2), inset 0 0 40px 20px rgba(255, 210, 80, 0.25); } + 50% { box-shadow: inset 0 0 120px 60px rgba(255, 150, 180, 0.5), inset 0 0 90px 45px rgba(255, 210, 80, 0.55); } +} + .pearl-shell-close { position: absolute; top: 8px; From 332a343285fcecdcb3f178abf13e6f4350b3da11 Mon Sep 17 00:00:00 2001 From: arthurcai Date: Thu, 19 Mar 2026 17:55:08 +0800 Subject: [PATCH 3/6] fix: fix photo grid --- frontend/src/components/dad/DadConsole.vue | 16 +- .../src/components/dad/DcMemoryCardDialog.vue | 119 ++++++++---- frontend/src/components/overlay/CarPage.vue | 18 +- .../src/components/overlay/WhisperPanel.vue | 174 +++++++++++++----- 4 files changed, 236 insertions(+), 91 deletions(-) diff --git a/frontend/src/components/dad/DadConsole.vue b/frontend/src/components/dad/DadConsole.vue index 2366150..d5167aa 100644 --- a/frontend/src/components/dad/DadConsole.vue +++ b/frontend/src/components/dad/DadConsole.vue @@ -74,6 +74,7 @@ :preview-url="proofPreviewUrl" :uploading="proofUploading" :error="completeDialogError" + :success="completeSuccess" @close="closeCompleteDialog" @upload="onProofFileChange" @submit-without-photo="submitCompleteWithoutPhoto" @@ -220,6 +221,7 @@ async function fetchTasks() { const [taskList, taskStats] = await Promise.all([getDailyTasks(), getTaskStats()]) tasks.value = taskList stats.value = taskStats + error.value = '' } async function pollTasks() { @@ -229,6 +231,7 @@ async function pollTasks() { if (stats.value?.xp !== taskStats.xp || stats.value?.level !== taskStats.level) { stats.value = taskStats } + if (error.value) error.value = '' } catch { /* ignore */ } } @@ -297,6 +300,7 @@ const proofFile = ref(null) const proofUploading = ref(false) const completeDialogError = ref('') const proofPreviewUrl = ref('') +const completeSuccess = ref(false) function resetProof() { if (proofPreviewUrl.value) { @@ -317,9 +321,17 @@ function closeCompleteDialog() { showCompleteDialog.value = false completeTarget.value = null completeDialogError.value = '' + completeSuccess.value = false resetProof() } +function showSuccessThenClose() { + completeSuccess.value = true + window.setTimeout(() => { + closeCompleteDialog() + }, 1800) +} + function onProofFileChange(e: Event) { const input = e.target as HTMLInputElement const file = input.files?.[0] ?? null @@ -340,7 +352,7 @@ async function submitCompleteWithoutPhoto() { try { const updated = await completeTask(id) tasks.value = tasks.value.map((t) => (t.id === id ? updated : t)) - closeCompleteDialog() + showSuccessThenClose() } catch (e) { completeDialogError.value = getErrorMessage(e) } finally { @@ -359,7 +371,7 @@ async function submitCompleteWithPhoto() { const uploaded = await uploadPhoto(proofFile.value, `任务证明:${completeTarget.value.title}`) const updated = await completeTask(id, { proof_photo_url: uploaded.image_url }) tasks.value = tasks.value.map((t) => (t.id === id ? updated : t)) - closeCompleteDialog() + showSuccessThenClose() } catch (e) { completeDialogError.value = getErrorMessage(e) } finally { diff --git a/frontend/src/components/dad/DcMemoryCardDialog.vue b/frontend/src/components/dad/DcMemoryCardDialog.vue index 930b9ed..ccc521e 100644 --- a/frontend/src/components/dad/DcMemoryCardDialog.vue +++ b/frontend/src/components/dad/DcMemoryCardDialog.vue @@ -1,48 +1,58 @@