feat: add welcome card

This commit is contained in:
Shivam Mishra
2026-06-29 18:11:43 +05:30
parent bb387dbd4a
commit ef63752075
5 changed files with 107 additions and 33 deletions
@@ -182,7 +182,8 @@ const handleCreateAssistant = () => {
</div>
</div>
<div class="flex gap-2">
<div class="flex items-center gap-2">
<slot name="headerActions" />
<slot name="search" />
<div
v-if="!showPaywall && buttonLabel"
@@ -0,0 +1,61 @@
<script setup>
import { computed } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
const currentUser = useMapGetter('getCurrentUser');
const firstName = computed(() => {
const name = currentUser.value?.name?.trim();
return name ? name.split(' ')[0] : 'there';
});
// The summary is generated by the model from the assistant's stats using the
// captain_overview_summary.liquid prompt (lib/integrations/openai/openai_prompts).
// DUMMY: stands in for an LLM-generated summary. In production this markdown
// string would come back from the model after we feed it the assistant's
// stats; numbers are emphasised with **bold** so we can highlight them.
const welcomeMarkdown = computed(
() =>
`Hey ${firstName.value}, your assistant handled **1,248 conversations** this month and saved your team **612 hours** of work. Auto-resolution climbed **4.1%** while keeping handoffs low. Knowledge coverage is running strong as well.\n\nI'd keep an eye out for credits, you seem to have exhaused nearly **80%** of it.`
);
// Split the markdown on **bold** runs so we can render emphasised numbers as
// brand-highlighted spans with Tailwind (instead of styling raw HTML).
const segments = computed(() => {
const parts = [];
const regex = /\*\*(.+?)\*\*/g;
let lastIndex = 0;
let match = regex.exec(welcomeMarkdown.value);
while (match) {
if (match.index > lastIndex) {
parts.push({ text: welcomeMarkdown.value.slice(lastIndex, match.index) });
}
parts.push({ text: match[1], bold: true });
lastIndex = regex.lastIndex;
match = regex.exec(welcomeMarkdown.value);
}
if (lastIndex < welcomeMarkdown.value.length) {
parts.push({ text: welcomeMarkdown.value.slice(lastIndex) });
}
return parts.map((part, index) => ({ ...part, key: index }));
});
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-1.5 text-n-slate-10">
<span class="i-lucide-sparkles size-3.5" />
<span class="text-xs">
{{ $t('CAPTAIN.OVERVIEW.WELCOME.LABEL') }}
</span>
</div>
<p class="text-lg leading-relaxed text-n-slate-12">
<template v-for="segment in segments" :key="segment.key">
<span v-if="segment.bold" class="font-bold tabular-nums text-n-brand">{{
segment.text
}}</span>
<template v-else>{{ segment.text }}</template>
</template>
</p>
</div>
</template>
@@ -394,15 +394,14 @@
"HEADER_KNOW_MORE": "Know more",
"OVERVIEW": {
"HEADER": "Overview",
"WELCOME": {
"LABEL": "Captain summary"
},
"INBOX_BANNER": {
"TEXT": "This assistant isn't connected to any inbox yet, so it won't respond to conversations.",
"ACTION": "Connect inbox",
"DISMISS": "Dismiss"
},
"PERFORMANCE": {
"TITLE": "Performance",
"SUBTITLE": "How this assistant is doing"
},
"RANGES": {
"DAYS": "{count}d"
},
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
import WelcomeCard from 'dashboard/components-next/captain/pageComponents/overview/WelcomeCard.vue';
import MetricCard from 'dashboard/components-next/captain/pageComponents/overview/MetricCard.vue';
import KnowledgeCard from 'dashboard/components-next/captain/pageComponents/overview/KnowledgeCard.vue';
import ResponseQualityCard from 'dashboard/components-next/captain/pageComponents/overview/ResponseQualityCard.vue';
@@ -80,38 +81,29 @@ const metrics = computed(() => [
:show-know-more="false"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
>
<template #headerActions>
<div class="flex items-center gap-1 p-1 rounded-lg bg-n-alpha-1 shrink-0">
<button
v-for="range in ranges"
:key="range"
type="button"
class="px-3 py-1 text-sm font-medium rounded-md transition-colors"
:class="
selectedRange === range
? 'bg-n-solid-active text-n-slate-12 shadow-sm'
: 'text-n-slate-11 hover:text-n-slate-12'
"
@click="selectedRange = range"
>
{{ $t('CAPTAIN.OVERVIEW.RANGES.DAYS', { count: range }) }}
</button>
</div>
</template>
<template #body>
<div class="flex flex-col gap-6">
<InboxBanner />
<div class="flex items-center justify-between gap-4">
<div class="flex flex-col gap-1">
<h3 class="text-base font-medium text-n-slate-12">
{{ $t('CAPTAIN.OVERVIEW.PERFORMANCE.TITLE') }}
</h3>
<p class="text-sm text-n-slate-11">
{{ $t('CAPTAIN.OVERVIEW.PERFORMANCE.SUBTITLE') }}
</p>
</div>
<div
class="flex items-center gap-1 p-1 rounded-lg bg-n-alpha-1 shrink-0"
>
<button
v-for="range in ranges"
:key="range"
type="button"
class="px-3 py-1 text-sm font-medium rounded-md transition-colors"
:class="
selectedRange === range
? 'bg-n-solid-active text-n-slate-12 shadow-sm'
: 'text-n-slate-11 hover:text-n-slate-12'
"
@click="selectedRange = range"
>
{{ $t('CAPTAIN.OVERVIEW.RANGES.DAYS', { count: range }) }}
</button>
</div>
</div>
<WelcomeCard />
<div
class="grid grid-cols-1 gap-px overflow-hidden border rounded-xl sm:grid-cols-2 lg:grid-cols-3 bg-n-weak border-n-weak"