refactor(desktop): import-session entry is a sidebar nav row; browser module named as a foreign_sessions sibling
- Sidebar: the entry joins SIDEBAR_NAV (same chrome, active state, data-tour handle as the other rows) instead of a one-off Button below the rail; i18n moves to sidebar.nav['session-import']. - Reuse common.retry / common.refresh / common.back instead of duplicating them under sessionImport in six locales. - hermes_cli/foreign_session_browser.py -> foreign_sessions_browser.py so it sorts as a sibling of the foreign_sessions module it extends.
This commit is contained in:
@@ -3,10 +3,9 @@ import { sortableKeyboardCoordinates } from '@dnd-kit/sortable'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import type * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router'
|
||||
import { useLocation } from 'react-router'
|
||||
|
||||
import { PlatformAvatar } from '@/app/messaging/platform-icon'
|
||||
import { SESSION_IMPORT_ROUTE } from '@/app/routes'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Codicon } from '@/components/ui/codicon'
|
||||
import { ContextMenu, ContextMenuContent, ContextMenuTrigger } from '@/components/ui/context-menu'
|
||||
@@ -140,6 +139,7 @@ import {
|
||||
ARTIFACTS_ROUTE,
|
||||
CRON_ROUTE,
|
||||
MESSAGING_ROUTE,
|
||||
SESSION_IMPORT_ROUTE,
|
||||
SIDEBAR_NAV_AREA,
|
||||
type SidebarNavContribution,
|
||||
SKILLS_ROUTE
|
||||
@@ -226,6 +226,12 @@ const SIDEBAR_NAV: SidebarNavItem[] = [
|
||||
icon: props => <Codicon name="watch" {...props} />,
|
||||
route: CRON_ROUTE,
|
||||
keybindActionId: 'nav.cron'
|
||||
},
|
||||
{
|
||||
id: 'session-import',
|
||||
label: '',
|
||||
icon: props => <Codicon name="cloud-download" {...props} />,
|
||||
route: SESSION_IMPORT_ROUTE
|
||||
}
|
||||
]
|
||||
|
||||
@@ -334,7 +340,6 @@ export function ChatSidebar({
|
||||
const { t } = useI18n()
|
||||
const s = t.sidebar
|
||||
const { pathname } = useLocation()
|
||||
const navigate = useNavigate()
|
||||
// Contributed nav rows (plugins pairing a page with a sidebar entry) render
|
||||
// below the built-ins with the same chrome; active = at their route.
|
||||
const navContributions = useContributions(SIDEBAR_NAV_AREA)
|
||||
@@ -1499,6 +1504,7 @@ export function ChatSidebar({
|
||||
(item.id === 'messaging' && currentView === 'messaging') ||
|
||||
(item.id === 'artifacts' && currentView === 'artifacts') ||
|
||||
(item.id === 'cron' && currentView === 'cron') ||
|
||||
(item.id === 'session-import' && currentView === 'session-import') ||
|
||||
// Contributed rows light up at their own route.
|
||||
(Boolean(item.route) && pathname === item.route)
|
||||
|
||||
@@ -1619,13 +1625,6 @@ export function ChatSidebar({
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
|
||||
<div className="shrink-0 px-3 py-2">
|
||||
<Button onClick={() => navigate(SESSION_IMPORT_ROUTE)} size="sm" variant="text">
|
||||
<Codicon name="cloud-download" />
|
||||
{t.sessionImport.action}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showSessionSections && (
|
||||
<div className="shrink-0 px-2 pb-1 pt-1">
|
||||
<SearchField
|
||||
|
||||
@@ -116,7 +116,7 @@ export function SessionImportView({ owner, onClose, onOpenSession }: SessionImpo
|
||||
rootClassName="bg-(--ui-bg-elevated)"
|
||||
titlebarActions={
|
||||
<Button
|
||||
aria-label={copy.refresh}
|
||||
aria-label={t.common.refresh}
|
||||
disabled={sessions.isFetching}
|
||||
onClick={() => {
|
||||
void sessions.refetch()
|
||||
@@ -183,7 +183,7 @@ export function SessionImportView({ owner, onClose, onOpenSession }: SessionImpo
|
||||
{sessions.isError && (
|
||||
<ErrorState className="p-5" description={copy.scanHelp} title={copy.scanError}>
|
||||
<Button onClick={() => void sessions.refetch()} variant="secondary">
|
||||
{copy.retry}
|
||||
{t.common.retry}
|
||||
</Button>
|
||||
</ErrorState>
|
||||
)}
|
||||
@@ -261,7 +261,7 @@ export function SessionImportView({ owner, onClose, onOpenSession }: SessionImpo
|
||||
<div className="px-8 pb-5 pt-6">
|
||||
<div className="mb-4 hidden max-[760px]:block">
|
||||
<Button onClick={() => setSelected(null)} variant="text">
|
||||
{copy.back}
|
||||
{t.common.back}
|
||||
</Button>
|
||||
</div>
|
||||
<h2 className="text-xl font-medium leading-7 tracking-tight">{current.title}</h2>
|
||||
@@ -274,7 +274,7 @@ export function SessionImportView({ owner, onClose, onOpenSession }: SessionImpo
|
||||
{preview.isError && (
|
||||
<ErrorState description={copy.previewHelp} title={copy.previewError}>
|
||||
<Button onClick={() => void preview.refetch()} variant="secondary">
|
||||
{copy.retry}
|
||||
{t.common.retry}
|
||||
</Button>
|
||||
</ErrorState>
|
||||
)}
|
||||
|
||||
@@ -162,7 +162,8 @@ export type CommandDispatchResponse =
|
||||
| SendCommandDispatchResponse
|
||||
| PrefillCommandDispatchResponse
|
||||
|
||||
export type SidebarNavId = 'artifacts' | 'command-center' | 'cron' | 'messaging' | 'new-session' | 'settings' | 'skills'
|
||||
export type SidebarNavId =
|
||||
'artifacts' | 'command-center' | 'cron' | 'messaging' | 'new-session' | 'session-import' | 'settings' | 'skills'
|
||||
|
||||
export interface SidebarNavItem {
|
||||
/** Built-in view id, or a contributed row's namespaced contribution id. */
|
||||
|
||||
@@ -2,7 +2,6 @@ import { defineLocale } from './define-locale'
|
||||
|
||||
export const ar = defineLocale({
|
||||
sessionImport: {
|
||||
refresh: 'تحديث الجلسات',
|
||||
title: 'المتابعة من تطبيق آخر',
|
||||
subtitle: 'انقل محادثة إلى Hermes وتابع من حيث توقفت.',
|
||||
action: 'استيراد جلسة',
|
||||
@@ -12,7 +11,6 @@ export const ar = defineLocale({
|
||||
all: 'الكل',
|
||||
search: 'البحث في الجلسات المحملة',
|
||||
scanning: 'جارٍ البحث عن المحادثات',
|
||||
retry: 'حاول مرة أخرى',
|
||||
scanError: 'تعذر العثور على الجلسات',
|
||||
scanHelp: 'تحقق من اتصال الخادم ثم أعد المحاولة. قد تحتاج الخوادم القديمة إلى تحديث.',
|
||||
empty: 'لا توجد محادثات',
|
||||
@@ -24,7 +22,6 @@ export const ar = defineLocale({
|
||||
messages: 'رسائل',
|
||||
choose: 'محادثة تستحق المتابعة',
|
||||
chooseHelp: 'اختر جلسة لقراءة سجلها قبل نقلها إلى Hermes.',
|
||||
back: 'العودة إلى الجلسات',
|
||||
previewLoading: 'جارٍ فتح المعاينة',
|
||||
previewError: 'المعاينة غير متاحة',
|
||||
previewHelp: 'ربما تم نقل الملف الأصلي أو تغييره. حدّث القائمة وحاول مرة أخرى.',
|
||||
@@ -1734,7 +1731,8 @@ export const ar = defineLocale({
|
||||
chat: 'المحادثة',
|
||||
settings: 'الإعدادات',
|
||||
cron: 'المهام المجدولة',
|
||||
agents: 'الوكلاء'
|
||||
agents: 'الوكلاء',
|
||||
'session-import': 'استيراد جلسة'
|
||||
},
|
||||
searchAria: 'البحث في الجلسات',
|
||||
searchPlaceholder: 'البحث في الجلسات...',
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { Translations } from './types'
|
||||
|
||||
export const en: Translations = {
|
||||
sessionImport: {
|
||||
refresh: 'Refresh sessions',
|
||||
title: 'Continue from another app',
|
||||
subtitle: 'Bring a conversation into Hermes and pick up where you left off.',
|
||||
action: 'Import session',
|
||||
@@ -14,7 +13,6 @@ export const en: Translations = {
|
||||
all: 'All',
|
||||
search: 'Search loaded sessions',
|
||||
scanning: 'Finding conversations',
|
||||
retry: 'Try again',
|
||||
scanError: 'Could not find sessions',
|
||||
scanHelp: 'Check your backend connection, then try again. Older backends may need an update.',
|
||||
empty: 'No conversations found',
|
||||
@@ -26,7 +24,6 @@ export const en: Translations = {
|
||||
messages: 'messages',
|
||||
choose: 'A conversation worth continuing',
|
||||
chooseHelp: 'Choose a session to read its history before bringing it into Hermes.',
|
||||
back: 'Back to sessions',
|
||||
previewLoading: 'Opening preview',
|
||||
previewError: 'Preview unavailable',
|
||||
previewHelp: 'The source may have moved or changed. Refresh the list and try again.',
|
||||
@@ -2360,7 +2357,8 @@ export const en: Translations = {
|
||||
skills: 'Capabilities',
|
||||
messaging: 'Messaging',
|
||||
artifacts: 'Artifacts',
|
||||
cron: 'Scheduled jobs'
|
||||
cron: 'Scheduled jobs',
|
||||
'session-import': 'Import session'
|
||||
},
|
||||
searchAria: 'Search sessions',
|
||||
searchPlaceholder: 'Search sessions…',
|
||||
|
||||
@@ -4,7 +4,6 @@ import { defineLocale } from './define-locale'
|
||||
|
||||
export const ja = defineLocale({
|
||||
sessionImport: {
|
||||
refresh: '一覧を更新',
|
||||
title: '別のアプリから続ける',
|
||||
subtitle: '会話をHermesに取り込み、続きを始めましょう。',
|
||||
action: 'セッションを取り込む',
|
||||
@@ -14,7 +13,6 @@ export const ja = defineLocale({
|
||||
all: 'すべて',
|
||||
search: '読み込み済みのセッションを検索',
|
||||
scanning: '会話を検索中',
|
||||
retry: '再試行',
|
||||
scanError: 'セッションを取得できません',
|
||||
scanHelp: 'バックエンドの接続を確認して再試行してください。古いバックエンドは更新が必要な場合があります。',
|
||||
empty: '会話が見つかりません',
|
||||
@@ -26,7 +24,6 @@ export const ja = defineLocale({
|
||||
messages: 'メッセージ',
|
||||
choose: '会話の続きを始めましょう',
|
||||
chooseHelp: 'セッションを選び、取り込む前に履歴を確認できます。',
|
||||
back: 'セッションに戻る',
|
||||
previewLoading: 'プレビューを開いています',
|
||||
previewError: 'プレビューできません',
|
||||
previewHelp: '元のファイルが移動または変更された可能性があります。一覧を更新してください。',
|
||||
@@ -2034,7 +2031,8 @@ export const ja = defineLocale({
|
||||
skills: 'スキルとツール',
|
||||
messaging: 'メッセージング',
|
||||
artifacts: 'アーティファクト',
|
||||
cron: 'スケジュール済みジョブ'
|
||||
cron: 'スケジュール済みジョブ',
|
||||
'session-import': 'セッションを取り込む'
|
||||
},
|
||||
searchAria: 'セッションを検索',
|
||||
searchPlaceholder: 'セッションを検索…',
|
||||
|
||||
@@ -25,7 +25,6 @@ const RU_NOUN = (count: number | string, one: string, few: string, many: string)
|
||||
|
||||
export const ru = defineLocale({
|
||||
sessionImport: {
|
||||
refresh: 'Обновить сессии',
|
||||
title: 'Продолжить из другого приложения',
|
||||
subtitle: 'Перенесите разговор в Hermes и продолжите с того места, где остановились.',
|
||||
action: 'Импортировать сессию',
|
||||
@@ -35,7 +34,6 @@ export const ru = defineLocale({
|
||||
all: 'Все',
|
||||
search: 'Поиск по загруженным сессиям',
|
||||
scanning: 'Поиск разговоров',
|
||||
retry: 'Повторить',
|
||||
scanError: 'Не удалось найти сессии',
|
||||
scanHelp: 'Проверьте подключение к серверу и повторите попытку. Старому серверу может требоваться обновление.',
|
||||
empty: 'Разговоров пока нет',
|
||||
@@ -47,7 +45,6 @@ export const ru = defineLocale({
|
||||
messages: 'сообщений',
|
||||
choose: 'Разговор, который стоит продолжить',
|
||||
chooseHelp: 'Выберите сессию, чтобы прочитать историю перед импортом в Hermes.',
|
||||
back: 'Назад к сессиям',
|
||||
previewLoading: 'Открываем просмотр',
|
||||
previewError: 'Просмотр недоступен',
|
||||
previewHelp: 'Исходный файл мог переместиться или измениться. Обновите список и повторите попытку.',
|
||||
@@ -2396,7 +2393,8 @@ export const ru = defineLocale({
|
||||
skills: 'Возможности',
|
||||
messaging: 'Сообщения',
|
||||
artifacts: 'Артефакты',
|
||||
cron: 'Запланированные задачи'
|
||||
cron: 'Запланированные задачи',
|
||||
'session-import': 'Импортировать сессию'
|
||||
},
|
||||
searchAria: 'Поиск сеансов',
|
||||
searchPlaceholder: 'Поиск сеансов…',
|
||||
|
||||
@@ -52,7 +52,6 @@ interface AuxTaskCopy {
|
||||
|
||||
export interface Translations {
|
||||
sessionImport: {
|
||||
refresh: string
|
||||
title: string
|
||||
subtitle: string
|
||||
action: string
|
||||
@@ -62,7 +61,6 @@ export interface Translations {
|
||||
all: string
|
||||
search: string
|
||||
scanning: string
|
||||
retry: string
|
||||
scanError: string
|
||||
scanHelp: string
|
||||
empty: string
|
||||
@@ -74,7 +72,6 @@ export interface Translations {
|
||||
messages: string
|
||||
choose: string
|
||||
chooseHelp: string
|
||||
back: string
|
||||
previewLoading: string
|
||||
previewError: string
|
||||
previewHelp: string
|
||||
|
||||
@@ -4,7 +4,6 @@ import { defineLocale } from './define-locale'
|
||||
|
||||
export const zhHant = defineLocale({
|
||||
sessionImport: {
|
||||
refresh: '重新整理工作階段',
|
||||
title: '從其他應用程式繼續',
|
||||
subtitle: '將對話匯入 Hermes,接著上次的進度繼續。',
|
||||
action: '匯入工作階段',
|
||||
@@ -14,7 +13,6 @@ export const zhHant = defineLocale({
|
||||
all: '全部',
|
||||
search: '搜尋已載入的工作階段',
|
||||
scanning: '正在尋找對話',
|
||||
retry: '重試',
|
||||
scanError: '無法尋找工作階段',
|
||||
scanHelp: '請檢查後端連線並重試。舊版後端可能需要更新。',
|
||||
empty: '找不到對話',
|
||||
@@ -26,7 +24,6 @@ export const zhHant = defineLocale({
|
||||
messages: '則訊息',
|
||||
choose: '繼續一段對話',
|
||||
chooseHelp: '選擇工作階段,在匯入 Hermes 前查看歷程記錄。',
|
||||
back: '返回工作階段',
|
||||
previewLoading: '正在開啟預覽',
|
||||
previewError: '無法預覽',
|
||||
previewHelp: '來源檔案可能已移動或變更。請重新整理清單後重試。',
|
||||
@@ -1959,7 +1956,8 @@ export const zhHant = defineLocale({
|
||||
skills: '技能與工具',
|
||||
messaging: '訊息平台',
|
||||
artifacts: '成品',
|
||||
cron: '排程工作'
|
||||
cron: '排程工作',
|
||||
'session-import': '匯入工作階段'
|
||||
},
|
||||
searchAria: '搜尋工作階段',
|
||||
searchPlaceholder: '搜尋工作階段…',
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { Translations } from './types'
|
||||
|
||||
export const zh: Translations = {
|
||||
sessionImport: {
|
||||
refresh: '刷新会话',
|
||||
title: '从其他应用继续',
|
||||
subtitle: '将对话导入 Hermes,接着上次的进度继续。',
|
||||
action: '导入会话',
|
||||
@@ -14,7 +13,6 @@ export const zh: Translations = {
|
||||
all: '全部',
|
||||
search: '搜索已加载的会话',
|
||||
scanning: '正在查找对话',
|
||||
retry: '重试',
|
||||
scanError: '无法查找会话',
|
||||
scanHelp: '请检查后端连接并重试。旧版后端可能需要更新。',
|
||||
empty: '未找到对话',
|
||||
@@ -26,7 +24,6 @@ export const zh: Translations = {
|
||||
messages: '条消息',
|
||||
choose: '继续一段对话',
|
||||
chooseHelp: '选择会话,在导入 Hermes 前查看历史记录。',
|
||||
back: '返回会话列表',
|
||||
previewLoading: '正在打开预览',
|
||||
previewError: '预览不可用',
|
||||
previewHelp: '源文件可能已移动或更改。请刷新列表后重试。',
|
||||
@@ -2526,7 +2523,8 @@ export const zh: Translations = {
|
||||
skills: '技能与工具',
|
||||
messaging: '消息平台',
|
||||
artifacts: '产物',
|
||||
cron: '定时任务'
|
||||
cron: '定时任务',
|
||||
'session-import': '导入会话'
|
||||
},
|
||||
searchAria: '搜索会话',
|
||||
searchPlaceholder: '搜索会话…',
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from hermes_cli.foreign_session_browser import list_foreign_sessions
|
||||
from hermes_cli.foreign_sessions_browser import list_foreign_sessions
|
||||
|
||||
|
||||
@pytest.mark.parametrize("operation", ["resolve", "stat"])
|
||||
@@ -60,7 +60,7 @@ def test_foreign_rpc_preview_import_and_profile_isolation(tmp_path, monkeypatch)
|
||||
|
||||
|
||||
def test_foreign_pages_confine_handles_and_failed_import_rolls_back(tmp_path, monkeypatch):
|
||||
from hermes_cli import foreign_session_browser as browser
|
||||
from hermes_cli import foreign_sessions_browser as browser
|
||||
from hermes_state import SessionDB
|
||||
|
||||
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
||||
|
||||
@@ -8,7 +8,7 @@ method = _registry.method
|
||||
|
||||
@method("session.foreign.list")
|
||||
def _foreign_list(rid, params):
|
||||
from hermes_cli.foreign_session_browser import list_foreign_sessions
|
||||
from hermes_cli.foreign_sessions_browser import list_foreign_sessions
|
||||
try:
|
||||
return _ok(rid, list_foreign_sessions(params.get("source"), params.get("offset", 0), params.get("limit", 25)))
|
||||
except ValueError as exc:
|
||||
@@ -18,7 +18,7 @@ def _foreign_list(rid, params):
|
||||
|
||||
|
||||
def _foreign_history_request(rid, params, importing):
|
||||
from hermes_cli.foreign_session_browser import import_browser_session, preview_foreign_session
|
||||
from hermes_cli.foreign_sessions_browser import import_browser_session, preview_foreign_session
|
||||
try:
|
||||
with _profile_db(params) as db:
|
||||
if db is None:
|
||||
|
||||
Reference in New Issue
Block a user