Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/renderer/settings/components/MemoryDiagnosticsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
</DcSectionCard>

<DcSectionCard
data-testid="archive-candidates"
:title="t('settings.memory.redesign.archiveCandidatesTitle')"
:description="t('settings.memory.redesign.archiveCandidatesDescription')"
>
Expand Down Expand Up @@ -195,12 +196,12 @@
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-muted-foreground">
<span>
{{ t('settings.deepchatAgents.memoryManager.health.archivePrediction.ageDays') }}:
{{ formatNumber(lifecycle.forget.ageDays) }}
{{ formatDays(lifecycle.forget.ageDays) }}
</span>
<span>
{{
t('settings.deepchatAgents.memoryManager.health.archivePrediction.decayScore')
}}: {{ formatNumber(lifecycle.forget.decayScore) }}
}}: {{ formatDecimal(lifecycle.forget.decayScore) }}
</span>
</div>
</li>
Expand Down Expand Up @@ -332,6 +333,7 @@ import type {
import { auditSentenceKey, formatRelativeTime } from './memoryRedesignUtils'
import MemoryInlineFeedback from './MemoryInlineFeedback.vue'
import { useMemoryInlineFeedback } from '../lib/useMemoryInlineFeedback'
import { useMemoryNumberFormatters } from '../lib/useMemoryNumberFormatters'

const props = defineProps<{
agentId: string
Expand All @@ -340,6 +342,8 @@ const props = defineProps<{
}>()

const { t, te, locale } = useI18n()
const { formatDecimal, formatDays } = useMemoryNumberFormatters()

const memoryClient = createMemoryClient()
const panelFeedback = useMemoryInlineFeedback('MemoryDiagnosticsPanel')
const feedback = panelFeedback.feedback
Expand Down Expand Up @@ -384,8 +388,12 @@ const reindexFailureReason = computed(() => {
return t('settings.memory.redesign.reindexInternalReason')
})
const recallDiagnostics = computed(() => health.value?.runtime.agent.retrieval.recall)
const recallLatencyP50 = computed(() => recallDiagnostics.value?.latencyMs.total.p50 ?? '—')
const recallLatencyP95 = computed(() => recallDiagnostics.value?.latencyMs.total.p95 ?? '—')
const recallLatencyP50 = computed(() =>
formatOptionalDecimal(recallDiagnostics.value?.latencyMs.total.p50)
)
const recallLatencyP95 = computed(() =>
formatOptionalDecimal(recallDiagnostics.value?.latencyMs.total.p95)
)
const fallbackCount = computed(() => {
const counts = recallDiagnostics.value?.degradationCounts
if (!counts) return 0
Expand Down Expand Up @@ -461,8 +469,8 @@ function shortId(id: string): string {
return id.length > 10 ? `${id.slice(0, 4)}…${id.slice(-6)}` : id
}

function formatNumber(value: number): string {
return Number.isFinite(value) ? value.toFixed(value >= 10 ? 0 : 2) : String(value)
function formatOptionalDecimal(value: number | null | undefined): string {
return value == null ? '—' : formatDecimal(value)
}

function eventLabel(eventType: string): string {
Expand Down
25 changes: 2 additions & 23 deletions src/renderer/settings/components/MemoryLifecyclePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ import { useI18n } from 'vue-i18n'
import { Icon } from '@iconify/vue'
import { DcBadge } from '@dc-ui/components/badge'
import type { MemoryLifecycle } from '@shared/contracts/routes'
import { useMemoryNumberFormatters } from '../lib/useMemoryNumberFormatters'

const props = defineProps<{
lifecycle: MemoryLifecycle | null
Expand All @@ -159,21 +160,7 @@ const props = defineProps<{
}>()

const { t, locale } = useI18n()

const decimalFormatter = computed(
() =>
new Intl.NumberFormat(locale.value || undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 3
})
)
const dayFormatter = computed(
() =>
new Intl.NumberFormat(locale.value || undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 1
})
)
const { formatDecimal: formatScore, formatDays } = useMemoryNumberFormatters()
const dateFormatter = computed(
() =>
new Intl.DateTimeFormat(locale.value || undefined, {
Expand Down Expand Up @@ -254,20 +241,12 @@ const archiveConditions = computed(() => {
]
})

function formatScore(value: number): string {
return decimalFormatter.value.format(value)
}

function formatOptionalScore(value: number | null): string {
return value === null
? t('settings.deepchatAgents.memoryManager.lifecycle.forget.notRefreshed')
: formatScore(value)
}

function formatDays(value: number): string {
return dayFormatter.value.format(value)
}

function formatTime(value: number): string {
return dateFormatter.value.format(new Date(value))
}
Expand Down
27 changes: 27 additions & 0 deletions src/renderer/settings/lib/useMemoryNumberFormatters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

export function useMemoryNumberFormatters() {
const { locale } = useI18n()
const decimalFormatter = computed(
() =>
new Intl.NumberFormat(locale.value || undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 3
})
)
const dayFormatter = computed(
() =>
new Intl.NumberFormat(locale.value || undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 1
})
)

const formatDecimal = (value: number): string =>
Number.isFinite(value) ? decimalFormatter.value.format(value) : String(value)
const formatDays = (value: number): string =>
Number.isFinite(value) ? dayFormatter.value.format(value) : String(value)

return { formatDecimal, formatDays }
}
40 changes: 33 additions & 7 deletions test/renderer/components/MemoryDiagnosticsPanel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,13 @@ describe('MemoryDiagnosticsPanel', () => {
expect(clearCopy.clearConfirmBody).toContain('Standing directives are kept')
})

it('renders Agent recall and process-wide pipeline pressure', async () => {
it('renders formatted diagnostics and process-wide pipeline pressure', async () => {
const health: MemoryHealthDto = structuredClone(baseHealth)
health.runtime.agent.retrieval.recall.latencyMs.total = {
samples: 4,
p50: 12,
p95: 48,
max: 50
p50: 685.7158329999074,
p95: Number.POSITIVE_INFINITY,
max: 804
}
health.runtime.agent.retrieval.recall.degradationCounts.vectorCold = 3
health.runtime.process.extractionQueue.depth = 2
Expand All @@ -199,18 +199,44 @@ describe('MemoryDiagnosticsPanel', () => {
health.runtime.process.providerAdmission.admissionDecisions.rateLimited = 2
const providerPressureSummary =
'Rate limit {rateLimited} · Capacity {capacityRejected} · Deadline {deadline} · Aborted {aborted} · Late settle {lateSettled}'
const { wrapper, t } = await setup(baseStatus, {
const { wrapper, memoryClient, t } = await setup(baseStatus, {
health,
messages: { 'settings.memory.redesign.providerPressureSummary': providerPressureSummary }
})
memoryClient.getArchiveCandidateLifecyclePreview.mockResolvedValueOnce({
...basePreview,
lifecycles: [
{
memoryId: 'memory-with-floating-point-diagnostics',
decayTier: 'archive_candidate',
forget: {
ageDays: 12.3456,
decayScore: 0.123456
}
}
]
})
await refreshButton(wrapper).trigger('click')
await flushPromises()

const pipeline = wrapper.get('[data-testid="runtime-pipeline"]')
expect(pipeline.text()).toContain('12')
expect(pipeline.text()).toContain('48')
const archiveCandidates = wrapper.get('[data-testid="archive-candidates"]')
const pipelineValues = pipeline.findAll('.tabular-nums').map((node) => node.text())
const archiveCandidateMetrics = archiveCandidates
.findAll('.text-muted-foreground > span')
.map((node) => node.text())
expect(pipelineValues).toContain('685.716')
expect(pipelineValues).toContain('Infinity')
expect(pipeline.text()).toContain('1250')
expect(pipeline.text()).toContain('7')
expect(pipeline.text()).toContain('Rate limit 2')
expect(pipeline.text()).toContain('Deadline 1')
expect(archiveCandidateMetrics).toContain(
'settings.deepchatAgents.memoryManager.health.archivePrediction.ageDays: 12.3'
)
expect(archiveCandidateMetrics).toContain(
'settings.deepchatAgents.memoryManager.health.archivePrediction.decayScore: 0.123'
)
expect(t).toHaveBeenCalledWith('settings.memory.redesign.providerPressureSummary', {
rateLimited: 2,
capacityRejected: 0,
Expand Down