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
35 changes: 31 additions & 4 deletions app/i18n.sync.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@
"list.running_of": "dafe09b6ae07",
"list.slots_free": "aa856cbf3070",
"list.title": "279b44d2ab4b",
"relative_time.days": "a41234397f3c",
"relative_time.hours": "520c737ea624",
"relative_time.just_now": "7ddb44d8a533",
"relative_time.minutes": "0a03167c208c",
"restart_stage.almost_body": "cc88c514c77b",
"restart_stage.almost_title": "0ceb8f97a4cc",
"restart_stage.deploying_body": "4cc2d4111a84",
Expand All @@ -59,6 +55,33 @@
"chat.starter_hint": "76085886cbdf",
"chat.you": "08b041935798"
},
"chat:ru": {
"history.empty_cta": "9c6a7d961b50",
"history.empty_hint": "3f95b708f4a5",
"history.empty_title": "0d60084f056e",
"history.lede": "7865d148c397",
"history.sync": "8d261a372fde",
"history.syncing": "8a046cc90ab0",
"history.title": "0e7696009337",
"message.helpful": "63c432db3ebb",
"message.hide": "ac20a57bfde0",
"message.not_helpful": "3c4ebf31165b",
"message.show": "0df6f1cad36c",
"message.summarized": "4ad03ef5cd63",
"session.export": "3664895579f0",
"session.fallback_title": "ccca18175753",
"session.insights_title": "97bd413b2e35",
"session.last_activity": "133983b2edcf",
"session.load_older": "2fd15b734915",
"session.loading": "ba3bbbe10d8b",
"session.message_count": "ff43d682a3fb",
"session.no_messages": "1e56e882e6e4",
"session.no_summary": "f098ecd68006",
"session.not_found_hint": "ca81b79f7f6d",
"session.not_found_title": "d8e4dcef4f3d",
"session.resolved": "dc676b428599",
"session.unresolved": "27a888c9d5e6"
},
"common:ru": {
"app_name": "d64517995073",
"auth.sign_in": "bfd402b2f6f3",
Expand Down Expand Up @@ -105,6 +128,10 @@
"nav.agents": "279b44d2ab4b",
"nav.history": "0e7696009337",
"nav.templates": "56b564b75c7f",
"relative_time.days": "a41234397f3c",
"relative_time.hours": "520c737ea624",
"relative_time.just_now": "7ddb44d8a533",
"relative_time.minutes": "0a03167c208c",
"steps.chat_body": "8f80dfeff166",
"steps.chat_title": "e5e09a778387",
"steps.deploy_body": "e358d64527e3",
Expand Down
6 changes: 0 additions & 6 deletions app/slices/agent/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,5 @@
"starting_body": "The container is pulling and initialising.",
"almost_title": "Almost ready…",
"almost_body": "Waiting for the agent to come online."
},
"relative_time": {
"just_now": "just now",
"minutes": "{count}m ago",
"hours": "{count}h ago",
"days": "{count}d ago"
}
}
6 changes: 0 additions & 6 deletions app/slices/agent/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,5 @@
"starting_body": "Контейнер загружается и инициализируется.",
"almost_title": "Почти готово…",
"almost_body": "Ожидание выхода агента в сеть."
},
"relative_time": {
"just_now": "только что",
"minutes": "{count} мин назад",
"hours": "{count} ч назад",
"days": "{count} дн назад"
}
}
44 changes: 30 additions & 14 deletions app/slices/chat/components/chat/detail/Provider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ onMounted(() => {
}
});

const heading = computed(() => session.value?.title?.trim() || 'Conversation');
const { locale } = useI18n();

const heading = computed(() => session.value?.title?.trim() || null);
function fmt(iso?: string | null): string {
return iso ? new Date(iso).toLocaleString() : '—';
return iso ? new Date(iso).toLocaleString(locale.value) : '—';
}

const sentimentVariant: Record<string, 'default' | 'secondary' | 'outline' | 'destructive'> = {
Expand All @@ -101,36 +103,50 @@ const sentimentVariant: Record<string, 'default' | 'secondary' | 'outline' | 'de
to="/chats"
class="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
>
<Icon name="arrow-left" :size="16" /> History
<Icon name="arrow-left" :size="16" /> {{ $t('history.title') }}
</NuxtLink>

<!-- Not found / not owned -->
<div
v-if="!session && !sessionPending"
class="rounded-xl border border-dashed bg-card/40 p-12 text-center"
>
<h2 class="text-base font-semibold">Conversation not found</h2>
<h2 class="text-base font-semibold">{{ $t('session.not_found_title') }}</h2>
<p class="mt-1 text-sm text-muted-foreground">
It may have been removed, or it isn't one of yours.
{{ $t('session.not_found_hint') }}
</p>
</div>

<template v-else-if="session">
<!-- Meta header -->
<div class="rounded-md border bg-card p-4">
<span class="text-lg font-semibold">{{ heading }}</span>
<span class="text-lg font-semibold">
{{ heading ?? $t('session.fallback_title') }}
</span>
<div
class="mt-1 flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground"
>
<span>{{ session.messageCount }} messages</span>
<span>Last activity {{ fmt(session.lastMessageAt) }}</span>
<span>
{{
$t(
'session.message_count',
{ count: session.messageCount },
session.messageCount,
)
}}
</span>
<span>
{{ $t('session.last_activity', { when: fmt(session.lastMessageAt) }) }}
</span>
</div>

<!-- LLM gist: summary + insights -->
<div class="mt-3 rounded bg-muted/40 p-3">
<div class="flex items-center justify-between gap-2">
<div class="flex flex-wrap items-center gap-1.5">
<span class="text-xs font-medium text-muted-foreground">Summary &amp; insights</span>
<span class="text-xs font-medium text-muted-foreground">
{{ $t('session.insights_title') }}
</span>
<!-- sentiment / resolved / language sit by the header, apart from topics -->
<template v-if="session.insights">
<Badge
Expand All @@ -140,7 +156,7 @@ const sentimentVariant: Record<string, 'default' | 'secondary' | 'outline' | 'de
{{ session.insights.sentiment }}
</Badge>
<Badge variant="outline" class="capitalize">
{{ session.insights.resolved ? 'resolved' : 'unresolved' }}
{{ $t(session.insights.resolved ? 'session.resolved' : 'session.unresolved') }}
</Badge>
<Badge variant="outline" class="capitalize">
{{ session.insights.language }}
Expand All @@ -152,7 +168,7 @@ const sentimentVariant: Record<string, 'default' | 'secondary' | 'outline' | 'de
{{ session.summary }}
</p>
<p v-else class="mt-2 text-sm text-muted-foreground">
No summary yet
{{ $t('session.no_summary') }}
</p>
<!-- Topic tags only -->
<div
Expand All @@ -173,7 +189,7 @@ const sentimentVariant: Record<string, 'default' | 'secondary' | 'outline' | 'de

<!-- Export controls -->
<div class="flex items-center justify-end gap-1.5">
<span class="text-xs text-muted-foreground">Export</span>
<span class="text-xs text-muted-foreground">{{ $t('session.export') }}</span>
<button
v-for="f in (['json', 'markdown', 'csv'] as ChatExportFormat[])"
:key="f"
Expand All @@ -197,15 +213,15 @@ const sentimentVariant: Record<string, 'default' | 'secondary' | 'outline' | 'de
:disabled="loading"
@click="loadOlder"
>
{{ loading ? 'Loading…' : 'Load older' }}
{{ $t(loading ? 'session.loading' : 'session.load_older') }}
</button>
</div>

<div
v-if="!messages.length && !loading"
class="py-10 text-center text-sm text-muted-foreground"
>
No messages in this conversation.
{{ $t('session.no_messages') }}
</div>

<ChatMessageBubble
Expand Down
29 changes: 19 additions & 10 deletions app/slices/chat/components/chat/list/Card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ const heading = computed(() => {
if (t) return t;
const s = props.session.summary?.trim();
if (s) return s.length > 80 ? `${s.slice(0, 80)}…` : s;
return 'Conversation';
return null;
});

const relative = computed(() => {
const relative = computed<{ key: string; count: number } | null>(() => {
const ts = Date.parse(props.session.lastMessageAt);
if (Number.isNaN(ts)) return null;
const diff = Date.now() - ts;
const minute = 60_000;
const hour = 60 * minute;
const day = 24 * hour;
if (diff < minute) return 'just now';
if (diff < hour) return `${Math.floor(diff / minute)}m ago`;
if (diff < day) return `${Math.floor(diff / hour)}h ago`;
return `${Math.floor(diff / day)}d ago`;
if (diff < minute) return { key: 'relative_time.just_now', count: 0 };
if (diff < hour) return { key: 'relative_time.minutes', count: Math.floor(diff / minute) };
if (diff < day) return { key: 'relative_time.hours', count: Math.floor(diff / hour) };
return { key: 'relative_time.days', count: Math.floor(diff / day) };
});
</script>

Expand All @@ -39,7 +39,9 @@ const relative = computed(() => {
<Icon name="message-square" :size="18" />
</div>
<div class="min-w-0 flex-1">
<h3 class="truncate text-base font-semibold">{{ heading }}</h3>
<h3 class="truncate text-base font-semibold">
{{ heading ?? $t('session.fallback_title') }}
</h3>
<p
v-if="session.preview"
class="mt-0.5 truncate text-xs text-muted-foreground"
Expand All @@ -54,10 +56,17 @@ const relative = computed(() => {
>
<span class="inline-flex items-center gap-1">
<Icon name="messages-square" :size="12" class="-mt-0.5" />
{{ session.messageCount }}
{{ session.messageCount === 1 ? 'message' : 'messages' }}
{{
$t(
'session.message_count',
{ count: session.messageCount },
session.messageCount,
)
}}
</span>
<span v-if="relative" class="shrink-0">
{{ $t(relative.key, { count: relative.count }) }}
</span>
<span v-if="relative" class="shrink-0">{{ relative }}</span>
</div>
</NuxtLink>
</template>
12 changes: 6 additions & 6 deletions app/slices/chat/components/chat/list/Provider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ async function onSync() {
<div class="flex flex-col gap-6">
<header class="flex flex-wrap items-start justify-between gap-3">
<div>
<h1 class="text-2xl font-bold tracking-tight">History</h1>
<h1 class="text-2xl font-bold tracking-tight">{{ $t('history.title') }}</h1>
<p class="mt-1 text-sm text-muted-foreground">
Your past conversations. Open one to pick up where you left off.
{{ $t('history.lede') }}
</p>
</div>
<button
Expand All @@ -41,7 +41,7 @@ async function onSync() {
:size="14"
:class="syncing ? 'animate-spin' : undefined"
/>
{{ syncing ? 'Syncing…' : 'Sync' }}
{{ $t(syncing ? 'history.syncing' : 'history.sync') }}
</button>
</header>

Expand Down Expand Up @@ -86,16 +86,16 @@ async function onSync() {
>
<Icon name="message-square" :size="22" />
</div>
<h2 class="mt-4 text-base font-semibold">No conversations yet</h2>
<h2 class="mt-4 text-base font-semibold">{{ $t('history.empty_title') }}</h2>
<p class="mt-1 text-sm text-muted-foreground max-w-sm mx-auto">
Chat with an agent and it'll show up here so you can revisit it later.
{{ $t('history.empty_hint') }}
</p>
<NuxtLink
to="/agents"
class="mt-5 inline-flex items-center gap-1.5 rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium hover:opacity-95 transition"
>
<Icon name="bot" :size="14" />
Browse agents
{{ $t('history.empty_cta') }}
</NuxtLink>
</div>
</div>
Expand Down
10 changes: 6 additions & 4 deletions app/slices/chat/components/chat/message/Bubble.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ const summaryText = computed(() =>
@click="summaryOpen = !summaryOpen"
>
<Icon name="file-text" :size="14" class="shrink-0" />
<span class="font-medium">Earlier in this conversation — summarized</span>
<span class="ml-auto text-xs">{{ summaryOpen ? 'Hide' : 'Show' }}</span>
<span class="font-medium">{{ $t('message.summarized') }}</span>
<span class="ml-auto text-xs">
{{ $t(summaryOpen ? 'message.hide' : 'message.show') }}
</span>
</button>
<p
v-if="summaryOpen"
Expand Down Expand Up @@ -83,7 +85,7 @@ const summaryText = computed(() =>
<div class="mt-1.5 flex items-center gap-0.5">
<button
type="button"
aria-label="Helpful"
:aria-label="$t('message.helpful')"
class="rounded p-1 text-muted-foreground transition-colors hover:text-foreground"
:class="rating === 1 && 'text-emerald-600'"
@click="emit('rate', 1)"
Expand All @@ -92,7 +94,7 @@ const summaryText = computed(() =>
</button>
<button
type="button"
aria-label="Not helpful"
:aria-label="$t('message.not_helpful')"
class="rounded p-1 text-muted-foreground transition-colors hover:text-foreground"
:class="rating === -1 && 'text-rose-600'"
@click="emit('rate', -1)"
Expand Down
33 changes: 33 additions & 0 deletions app/slices/chat/i18n/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"history": {
"title": "History",
"lede": "Your past conversations. Open one to pick up where you left off.",
"sync": "Sync",
"syncing": "Syncing…",
"empty_title": "No conversations yet",
"empty_hint": "Chat with an agent and it'll show up here so you can revisit it later.",
"empty_cta": "Browse agents"
},
"session": {
"fallback_title": "Conversation",
"message_count": "no messages | {count} message | {count} messages",
"not_found_title": "Conversation not found",
"not_found_hint": "It may have been removed, or it isn't one of yours.",
"last_activity": "Last activity {when}",
"insights_title": "Summary & insights",
"no_summary": "No summary yet",
"resolved": "resolved",
"unresolved": "unresolved",
"export": "Export",
"load_older": "Load older",
"loading": "Loading…",
"no_messages": "No messages in this conversation."
},
"message": {
"summarized": "Earlier in this conversation — summarized",
"show": "Show",
"hide": "Hide",
"helpful": "Helpful",
"not_helpful": "Not helpful"
}
}
33 changes: 33 additions & 0 deletions app/slices/chat/i18n/locales/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"history": {
"title": "История",
"lede": "Ваши прошлые беседы. Откройте любую, чтобы продолжить с того места, где остановились.",
"sync": "Синхронизировать",
"syncing": "Синхронизация…",
"empty_title": "Пока нет бесед",
"empty_hint": "Начните беседу с агентом — она появится здесь, и вы сможете вернуться к ней позже.",
"empty_cta": "Смотреть агентов"
},
"session": {
"fallback_title": "Беседа",
"message_count": "нет сообщений | {count} сообщение | {count} сообщения | {count} сообщений",
"not_found_title": "Беседа не найдена",
"not_found_hint": "Возможно, она удалена или принадлежит не вам.",
"last_activity": "Последняя активность {when}",
"insights_title": "Сводка и выводы",
"no_summary": "Сводки пока нет",
"resolved": "решено",
"unresolved": "не решено",
"export": "Экспорт",
"load_older": "Загрузить ранние",
"loading": "Загрузка…",
"no_messages": "В этой беседе нет сообщений."
},
"message": {
"summarized": "Ранее в этой беседе — сводка",
"show": "Показать",
"hide": "Скрыть",
"helpful": "Полезно",
"not_helpful": "Не полезно"
}
}
Loading
Loading