diff --git a/apps/server/docs/ai-context/metrics-ownership.md b/apps/server/docs/ai-context/metrics-ownership.md index 8b3f82c4b..c12a0c03e 100644 --- a/apps/server/docs/ai-context/metrics-ownership.md +++ b/apps/server/docs/ai-context/metrics-ownership.md @@ -125,6 +125,9 @@ | Activation / Retention | `first_model_selected` / `model_switched` | 前端(consciousness store watcher) | `packages/stage-ui/src/stores/analytics/index.ts` | PostHog | | Retention | `character_created` | 前端 | `apps/stage-web/src/pages/settings/characters/components/CharacterDialog.vue` | PostHog | | Retention | `chat_session_started` | 前端 | `packages/stage-ui/src/components/scenarios/chat/components/sessions-drawer.vue` | PostHog | +| Conversation controls | `chat_session_selected` | 前端 | `packages/stage-ui/src/components/scenarios/chat/components/sessions-drawer.vue` | PostHog | +| Conversation controls | `chat_message_deleted` / `chat_messages_cleared` / `chat_message_retried` | 前端 | `packages/stage-layouts/src/components/Layouts/*InteractiveArea.vue` / `packages/stage-layouts/src/components/Widgets/ChatActionButtons.vue` / `apps/stage-tamagotchi/src/renderer/components/InteractiveArea.vue` | PostHog | +| Conversation controls | `tts_stop_clicked` | 前端 | `packages/stage-layouts/src/composables/useStopSpeakingButton.ts` | PostHog | | Churn | `subscription_cancelled`(带 cancellation_reason) | 外部 Stripe 数据源 | PostHog Stripe source connector | Stripe/Postgres | | 老事件 | `provider_card_clicked` / `first_message_sent` | 前端 | `packages/stage-ui/src/composables/use-analytics.ts` | PostHog | diff --git a/apps/stage-tamagotchi/src/renderer/components/InteractiveArea.vue b/apps/stage-tamagotchi/src/renderer/components/InteractiveArea.vue index 620e4d9d6..1b3b350b5 100644 --- a/apps/stage-tamagotchi/src/renderer/components/InteractiveArea.vue +++ b/apps/stage-tamagotchi/src/renderer/components/InteractiveArea.vue @@ -5,6 +5,7 @@ import type { ChatHistoryItem } from '@proj-airi/stage-ui/types/chat' import { errorMessageFrom } from '@moeru/std' import { useStopSpeakingButton } from '@proj-airi/stage-layouts/composables/useStopSpeakingButton' import { ChatHistory, JournalPreviewModal } from '@proj-airi/stage-ui/components' +import { useAnalytics } from '@proj-airi/stage-ui/composables/use-analytics' import { useBackgroundStore } from '@proj-airi/stage-ui/stores/background' import { useChatOrchestratorStore } from '@proj-airi/stage-ui/stores/chat' import { useChatSessionStore } from '@proj-airi/stage-ui/stores/chat/session-store' @@ -57,7 +58,12 @@ const sendModeLabels = computed>(() => ({ 'ctrl-enter': t('stage.send-mode.ctrl-enter'), 'double-enter': t('stage.send-mode.double-enter'), })) -const { showStopSpeakingButton, stopSpeakingFromChat } = useStopSpeakingButton() +const { + trackChatMessageDeleted, + trackChatMessageRetried, + trackChatMessagesCleared, +} = useAnalytics() +const { showStopSpeakingButton, stopSpeakingFromChat } = useStopSpeakingButton('electron') const latestImageEntries = computed(() => { if (!activeCardId.value) @@ -198,7 +204,13 @@ watch(sendMode, () => { const historyMessages = computed(() => messages.value as unknown as ChatHistoryItem[]) async function handleDeleteMessage(index: number) { + const message = messages.value[index] await chatSyncStore.requestDeleteMessage({ index }) + trackChatMessageDeleted({ + surface: 'electron', + source: 'history', + message_role: message?.role ?? 'unknown', + }) } onMounted(() => { @@ -210,6 +222,20 @@ async function handleRetryMessage(index: number) { sessionId: chatSession.activeSessionId, index, }) + trackChatMessageRetried({ + surface: 'electron', + source: 'history', + }) +} + +async function handleCleanupMessages() { + const messageCount = messages.value.filter(message => message.role !== 'system').length + await chatSyncStore.requestCleanup() + trackChatMessagesCleared({ + surface: 'electron', + source: 'chat_controls', + message_count: messageCount, + }) } @@ -351,7 +377,7 @@ async function handleRetryMessage(index: number) { hover:text="red-500 dark:red-400" flex items-center justify-center rounded-md p-2 outline-none transition-colors transition-transform active:scale-95 - @click="() => chatSyncStore.requestCleanup()" + @click="handleCleanupMessages" >
diff --git a/packages/stage-layouts/src/components/Layouts/InteractiveArea.vue b/packages/stage-layouts/src/components/Layouts/InteractiveArea.vue index f33465c2e..e515ed2cb 100644 --- a/packages/stage-layouts/src/components/Layouts/InteractiveArea.vue +++ b/packages/stage-layouts/src/components/Layouts/InteractiveArea.vue @@ -2,6 +2,7 @@ import type { ChatHistoryItem } from '@proj-airi/stage-ui/types/chat' import { ChatHistory } from '@proj-airi/stage-ui/components' +import { useAnalytics } from '@proj-airi/stage-ui/composables/use-analytics' import { useChatOrchestratorStore } from '@proj-airi/stage-ui/stores/chat' import { useChatSessionStore } from '@proj-airi/stage-ui/stores/chat/session-store' import { useChatStreamStore } from '@proj-airi/stage-ui/stores/chat/stream-store' @@ -20,9 +21,16 @@ const { streamingMessage } = storeToRefs(useChatStreamStore()) const isLoading = ref(true) const historyMessages = computed(() => messages.value as unknown as ChatHistoryItem[]) +const { trackChatMessageDeleted } = useAnalytics() function handleDeleteMessage(index: number) { + const message = messages.value[index] messages.value = messages.value.filter((_, messageIndex) => messageIndex !== index) + trackChatMessageDeleted({ + surface: 'web', + source: 'history', + message_role: message?.role ?? 'unknown', + }) } diff --git a/packages/stage-layouts/src/components/Layouts/MobileInteractiveArea.vue b/packages/stage-layouts/src/components/Layouts/MobileInteractiveArea.vue index 09ced3276..81b6323f5 100644 --- a/packages/stage-layouts/src/components/Layouts/MobileInteractiveArea.vue +++ b/packages/stage-layouts/src/components/Layouts/MobileInteractiveArea.vue @@ -6,7 +6,7 @@ import { isStageTamagotchi } from '@proj-airi/stage-shared' import { useThreeViewControl } from '@proj-airi/stage-ui-three' import { ChatHistory, HearingConfigDialog } from '@proj-airi/stage-ui/components' import { ChatSessionsDrawer } from '@proj-airi/stage-ui/components/scenarios/chat' -import { useAudioAnalyzer } from '@proj-airi/stage-ui/composables' +import { useAnalytics, useAudioAnalyzer } from '@proj-airi/stage-ui/composables' import { useAudioContext } from '@proj-airi/stage-ui/stores/audio' import { useChatOrchestratorStore } from '@proj-airi/stage-ui/stores/chat' import { useChatMaintenanceStore } from '@proj-airi/stage-ui/stores/chat/maintenance' @@ -40,9 +40,26 @@ const { messages } = storeToRefs(chatSession) const { streamingMessage } = storeToRefs(chatStream) const { sending } = storeToRefs(chatOrchestrator) const historyMessages = computed(() => messages.value as unknown as ChatHistoryItem[]) +const { trackChatMessageDeleted, trackChatMessagesCleared } = useAnalytics() function handleDeleteMessage(index: number) { + const message = messages.value[index] messages.value = messages.value.filter((_, messageIndex) => messageIndex !== index) + trackChatMessageDeleted({ + surface: 'mobile', + source: 'history', + message_role: message?.role ?? 'unknown', + }) +} + +function handleCleanupMessages() { + const messageCount = messages.value.filter(message => message.role !== 'system').length + cleanupMessages() + trackChatMessagesCleared({ + surface: 'mobile', + source: 'chat_controls', + message_count: messageCount, + }) } const messageInput = ref('') @@ -77,7 +94,7 @@ const { isListening, startStreamingTranscription, stopStreamingTranscription } = isStageTamagotchi, }, ) -const { showStopSpeakingButton, stopSpeakingFromChat } = useStopSpeakingButton() +const { showStopSpeakingButton, stopSpeakingFromChat } = useStopSpeakingButton('mobile') const toggleTranscription = () => isListening.value ? stopStreamingTranscription() : startStreamingTranscription() async function handleSubmit() { @@ -232,7 +249,7 @@ onMounted(() => { bg="neutral-50/70 dark:neutral-800/70" w-fit flex items-center self-end justify-center rounded-xl p-2 backdrop-blur-md title="Cleanup Messages" - @click="cleanupMessages()" + @click="handleCleanupMessages" >
diff --git a/packages/stage-layouts/src/components/Widgets/ChatActionButtons.vue b/packages/stage-layouts/src/components/Widgets/ChatActionButtons.vue index d0133d488..1871be0c4 100644 --- a/packages/stage-layouts/src/components/Widgets/ChatActionButtons.vue +++ b/packages/stage-layouts/src/components/Widgets/ChatActionButtons.vue @@ -1,6 +1,9 @@