feat(memory): implement stability stage
Some checks failed
CI / validate (push) Has been cancelled
CI / validate (pull_request) Has been cancelled

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-09-02 15:04:18 +05:00
parent 4cd0b7b390
commit 031d094867
24 changed files with 1661 additions and 79 deletions

File diff suppressed because one or more lines are too long

View File

@@ -13,12 +13,17 @@ interface DraftSession {
generated_world?: { title?: string; premise?: string } | null
updated_at: string
}
interface UsageSummary {
quota: { usedToday: number; dailyRequestLimit: number; dailyTokenLimit: number }
totals: { requests: number; inputTokens: number; outputTokens: number; costUsd: number; averageLatencyMs: number }
}
const { api } = useDngApi()
const auth = useDngAuth()
const filter = ref<'all' | 'active' | 'drafts'>('all')
const campaigns = ref<CampaignRow[]>([])
const drafts = ref<DraftSession[]>([])
const usage = ref<UsageSummary | null>(null)
const loading = ref(true)
const error = ref('')
const renamingCampaignId = ref<string | null>(null)
@@ -43,6 +48,10 @@ function draftSummary(draft: DraftSession) {
|| 'Continue your conversation with the coauthor.'
}
function compactNumber(value: number) {
return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(value)
}
function openRename(campaign: CampaignRow) {
renamingCampaignId.value = campaign.id
newName.value = campaign.title
@@ -74,12 +83,14 @@ async function requestDelete(campaign: CampaignRow) {
onMounted(async () => {
try {
await auth.restore()
const [campaignResult, draftResult] = await Promise.all([
const [campaignResult, draftResult, usageResult] = await Promise.all([
api<{ campaigns: CampaignRow[] }>('/api/v1/campaigns'),
api<{ sessions: DraftSession[] }>('/api/v1/coauthor/sessions'),
api<UsageSummary>('/api/v1/usage').catch(() => null),
])
campaigns.value = campaignResult.campaigns
drafts.value = draftResult.sessions
usage.value = usageResult
} catch (cause) {
const value = cause as { data?: { statusMessage?: string }; message?: string }
error.value = value.data?.statusMessage ?? value.message ?? 'Could not load your campaigns.'
@@ -97,6 +108,13 @@ onMounted(async () => {
<NuxtLink to="/worlds/new" class="create-button"><span></span> CREATE A UNIVERSE</NuxtLink>
</section>
<section v-if="usage" class="usage-deck" aria-label="AI usage dashboard">
<div><small>AI REQUESTS / TODAY</small><b>{{ usage.quota.usedToday }}<em>/ {{ usage.quota.dailyRequestLimit }}</em></b></div>
<div><small>TOKENS / 30 DAYS</small><b>{{ compactNumber(usage.totals.inputTokens + usage.totals.outputTokens) }}</b></div>
<div><small>EST. COST / 30 DAYS</small><b>${{ usage.totals.costUsd.toFixed(3) }}</b></div>
<div><small>AVG. AI LATENCY</small><b>{{ (usage.totals.averageLatencyMs / 1000).toFixed(1) }}s</b></div>
</section>
<div class="filter-row">
<button v-for="item in ['all','active','drafts']" :key="item" :class="{active:filter===item}" @click="filter=item as typeof filter">{{ item }}</button>
<span>{{ campaigns.length }} CAMPAIGNS · {{ drafts.length }} DRAFTS</span>
@@ -152,6 +170,7 @@ onMounted(async () => {
.dash-head p{color:var(--muted)}
.create-button{display:flex;align-items:center;gap:18px;padding:18px 22px;background:var(--acid);color:#090909;text-decoration:none;font:600 10px var(--mono);letter-spacing:.12em}
.create-button span{font-size:20px}
.usage-deck{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin-top:34px;border:1px solid var(--line);background:#0d0d0c}.usage-deck>div{display:grid;gap:10px;padding:18px 20px;border-right:1px solid var(--line)}.usage-deck>div:last-child{border-right:0}.usage-deck small{font:500 7px var(--mono);letter-spacing:.14em;color:var(--muted)}.usage-deck b{font:600 20px var(--mono);color:var(--acid)}.usage-deck em{margin-left:4px;color:var(--muted);font:500 9px var(--mono);font-style:normal}
.filter-row{display:flex;align-items:center;gap:10px;margin:58px 0 24px;border-bottom:1px solid var(--line)}
.filter-row button{padding:0 4px 16px;margin-right:18px;background:none;border:0;color:var(--muted);font:500 9px var(--mono);text-transform:uppercase;letter-spacing:.14em}
.filter-row button.active{color:var(--ink);border-bottom:2px solid var(--acid)}
@@ -188,6 +207,6 @@ onMounted(async () => {
.rename-panel input:focus{border-color:var(--acid)}
.rename-actions{display:flex;justify-content:flex-end;gap:10px}
@media(max-width:1050px){.world-grid{grid-template-columns:1fr}}
@media(max-width:850px){.dash-head{align-items:start;flex-direction:column}.active-world{grid-template-columns:1fr}.world-art{min-height:280px}.system-strip{gap:16px;flex-wrap:wrap}}
@media(max-width:850px){.dash-head{align-items:start;flex-direction:column}.usage-deck{grid-template-columns:repeat(2,1fr)}.usage-deck>div:nth-child(2){border-right:0}.usage-deck>div:nth-child(-n+2){border-bottom:1px solid var(--line)}.active-world{grid-template-columns:1fr}.world-art{min-height:280px}.system-strip{gap:16px;flex-wrap:wrap}}
@media(max-width:520px){.dash-head h1{font-size:clamp(34px,12vw,52px)}.create-button{width:100%;justify-content:center}.filter-row>span{display:none}.world-info,.new-card{padding:28px}.active-world{min-height:580px}.world-info div{align-items:flex-start;flex-direction:column}.system-strip{flex-direction:column}}
</style>