@geminilight/mindos 0.6.58 → 0.6.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +24 -24
- package/_standalone/.next/build-manifest.json +3 -3
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/react-loadable-manifest.json +1 -1
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +2 -2
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js +3 -3
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +2 -2
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +3 -3
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +2 -2
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +2 -2
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +2 -2
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +2 -8
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +2 -2
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +3 -3
- package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +3 -3
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js +2 -2
- package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +24 -24
- package/_standalone/.next/server/chunks/3484.js +1 -1
- package/_standalone/.next/server/chunks/530.js +65 -66
- package/_standalone/.next/server/chunks/{6793.js → 8343.js} +2 -2
- package/_standalone/.next/server/chunks/9787.js +2 -0
- package/_standalone/.next/server/middleware-build-manifest.js +1 -1
- package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/{1814.a7c127b2c73d1f70.js → 1814.a79b84d37df75c43.js} +1 -1
- package/_standalone/.next/static/chunks/3427-2e61a5df1f5e55fb.js +1 -0
- package/_standalone/.next/static/chunks/{1053-fe009233cff06e72.js → 5581-dac72e9f16e5ea29.js} +3 -3
- package/_standalone/.next/static/chunks/6297-085daa21037d5f81.js +1 -0
- package/_standalone/.next/static/chunks/{7249-fa98ca10e9a10f39.js → 7249-6cf8f2b78718c59e.js} +1 -1
- package/_standalone/.next/static/chunks/8520-56ec9ff087c15204.js +22 -0
- package/_standalone/.next/static/chunks/9905-a19d379cb225246e.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/{page-7bdeab5af8e4f5f2.js → page-35ea6de1af2be3b5.js} +1 -1
- package/_standalone/.next/static/chunks/app/agents/page-b172ea3743adb047.js +1 -0
- package/_standalone/.next/static/chunks/app/changes/{page-5a72144d1080a699.js → page-6d2f49651c0061f7.js} +1 -1
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-84b95256f6e38aae.js +11 -0
- package/_standalone/.next/static/chunks/app/explore/{page-d3d99308146c2240.js → page-d9f58000bc445360.js} +2 -2
- package/_standalone/.next/static/chunks/app/help/{page-222df603080b5fab.js → page-f8cb806371b3175f.js} +1 -1
- package/_standalone/.next/static/chunks/app/inbox/history/{page-07819cf95cb0805f.js → page-26e71fb6f716a4c4.js} +1 -1
- package/_standalone/.next/static/chunks/app/layout-b89b0d955f39a753.js +164 -0
- package/_standalone/.next/static/chunks/app/login/{page-0eeef685052869a6.js → page-18fb00d568cd1f0e.js} +1 -1
- package/_standalone/.next/static/chunks/app/page-a8e6f085f38388bf.js +1 -0
- package/_standalone/.next/static/chunks/app/setup/{page-99fcfc460fa29733.js → page-821714e7477be46c.js} +1 -1
- package/_standalone/.next/static/chunks/app/trash/{page-54cbd5c98d9de69b.js → page-f92b728b78ac0f7e.js} +1 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/{not-found-fc04c2bd4f35bc6f.js → not-found-6e0c75ad26ce8572.js} +1 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-f87f4901b5e1a88f.js +12 -0
- package/_standalone/.next/static/chunks/app/wiki/page-641edb1f3cff2f93.js +1 -0
- package/_standalone/.next/static/chunks/{webpack-2c19436659aa657b.js → webpack-72e8d9e9073fd1f9.js} +1 -1
- package/_standalone/.next/static/css/6c104b118d3bc9b7.css +1 -0
- package/_standalone/.next/trace +64 -64
- package/_standalone/app/globals.css +2 -1
- package/_standalone/components/AskFab.tsx +4 -4
- package/_standalone/components/AskModal.tsx +1 -1
- package/_standalone/components/GuideCard.tsx +101 -152
- package/_standalone/components/RightAskPanel.tsx +2 -2
- package/_standalone/components/ask/AskContent.tsx +90 -51
- package/_standalone/components/ask/AskHeader.tsx +218 -18
- package/_standalone/components/ask/MessageList.tsx +66 -47
- package/_standalone/components/ask/SessionHistory.tsx +86 -60
- package/_standalone/components/ask/SessionTabBar.tsx +29 -21
- package/_standalone/components/ask/ThinkingBlock.tsx +6 -5
- package/_standalone/components/ask/ToolCallBlock.tsx +10 -9
- package/_standalone/components/settings/SettingsContent.tsx +1 -1
- package/_standalone/data/skills/mindos/SKILL.md +67 -15
- package/_standalone/data/skills/mindos-zh/SKILL.md +67 -11
- package/_standalone/hooks/useAskSession.ts +23 -1
- package/_standalone/lib/stores/locale-store.ts +20 -6
- package/_standalone/tsconfig.tsbuildinfo +1 -1
- package/app/app/globals.css +2 -1
- package/app/app/layout.tsx +16 -4
- package/app/components/AskFab.tsx +4 -4
- package/app/components/AskModal.tsx +1 -1
- package/app/components/GuideCard.tsx +101 -152
- package/app/components/HomeContent.tsx +116 -575
- package/app/components/RightAskPanel.tsx +2 -2
- package/app/components/WikiHomeContent.tsx +151 -3
- package/app/components/ask/AskContent.tsx +90 -51
- package/app/components/ask/AskHeader.tsx +218 -18
- package/app/components/ask/MessageList.tsx +66 -47
- package/app/components/ask/SessionHistory.tsx +86 -60
- package/app/components/ask/SessionTabBar.tsx +29 -21
- package/app/components/ask/ThinkingBlock.tsx +6 -5
- package/app/components/ask/ToolCallBlock.tsx +10 -9
- package/app/components/settings/SettingsContent.tsx +1 -1
- package/app/data/skills/mindos/SKILL.md +67 -15
- package/app/data/skills/mindos-zh/SKILL.md +67 -11
- package/app/hooks/useAskSession.ts +23 -1
- package/app/lib/i18n/modules/ai-chat.ts +97 -10
- package/app/lib/i18n/modules/onboarding.ts +12 -12
- package/app/lib/stores/LocaleStoreInit.tsx +24 -1
- package/app/lib/stores/locale-store.ts +20 -6
- package/app/lib/types.ts +1 -0
- package/package.json +1 -1
- package/skills/mindos/SKILL.md +67 -15
- package/skills/mindos/references/knowledge-health.md +120 -0
- package/skills/mindos-max/SKILL.md +52 -5
- package/skills/mindos-max-zh/SKILL.md +55 -6
- package/skills/mindos-zh/SKILL.md +67 -11
- package/_standalone/.next/server/chunks/2364.js +0 -2
- package/_standalone/.next/server/chunks/357.js +0 -1
- package/_standalone/.next/static/chunks/178-105779afb62d36d9.js +0 -1
- package/_standalone/.next/static/chunks/2218-d54538000574ffef.js +0 -1
- package/_standalone/.next/static/chunks/2549-e63cf57fa927a41d.js +0 -1
- package/_standalone/.next/static/chunks/9274-296ab35f9f09e42e.js +0 -1
- package/_standalone/.next/static/chunks/app/agents/page-5d1446665ddb3801.js +0 -1
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-b0103509ce34444b.js +0 -11
- package/_standalone/.next/static/chunks/app/layout-7e02ddf4144b01f1.js +0 -186
- package/_standalone/.next/static/chunks/app/page-6a6a12bd6d6812d0.js +0 -7
- package/_standalone/.next/static/chunks/app/view/[...path]/page-ca7bdcbf27f88a46.js +0 -12
- package/_standalone/.next/static/chunks/app/wiki/page-d492256a93f0b8bc.js +0 -1
- package/_standalone/.next/static/css/fd84c8316ead16eb.css +0 -1
- /package/_standalone/.next/static/{2ksXveDzEcnCMRIElDkLq → u8p6oIRTcr_ns-ElNZ9rl}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{2ksXveDzEcnCMRIElDkLq → u8p6oIRTcr_ns-ElNZ9rl}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { memo } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import { memo, useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { Sparkles, SquarePen, History, X, Maximize2, Minimize2, PanelRight, AppWindow, ChevronDown, Check, Trash2, Pencil, Pin, PinOff } from 'lucide-react';
|
|
3
4
|
import { useLocale } from '@/lib/stores/locale-store';
|
|
5
|
+
import type { ChatSession } from '@/lib/types';
|
|
6
|
+
import { sessionTitle } from '@/hooks/useAskSession';
|
|
4
7
|
|
|
5
8
|
interface AskHeaderProps {
|
|
6
9
|
isPanel: boolean;
|
|
@@ -13,49 +16,246 @@ interface AskHeaderProps {
|
|
|
13
16
|
askMode?: 'panel' | 'popup';
|
|
14
17
|
onModeSwitch?: () => void;
|
|
15
18
|
onClose?: () => void;
|
|
19
|
+
hideTitle?: boolean;
|
|
20
|
+
/** Session switching — inline in header when >=2 sessions */
|
|
21
|
+
sessions?: ChatSession[];
|
|
22
|
+
activeSessionId?: string | null;
|
|
23
|
+
onLoadSession?: (id: string) => void;
|
|
24
|
+
onDeleteSession?: (id: string) => void;
|
|
25
|
+
onRenameSession?: (id: string, name: string) => void;
|
|
26
|
+
onTogglePinSession?: (id: string) => void;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export default memo(function AskHeader({
|
|
19
30
|
isPanel, showHistory, onToggleHistory, onReset, isLoading,
|
|
20
|
-
maximized, onMaximize, askMode, onModeSwitch, onClose,
|
|
31
|
+
maximized, onMaximize, askMode, onModeSwitch, onClose, hideTitle,
|
|
32
|
+
sessions, activeSessionId, onLoadSession, onDeleteSession, onRenameSession, onTogglePinSession,
|
|
21
33
|
}: AskHeaderProps) {
|
|
22
34
|
const { t } = useLocale();
|
|
23
|
-
const iconSize =
|
|
35
|
+
const iconSize = 14;
|
|
36
|
+
const hasMultipleSessions = sessions && sessions.length >= 1;
|
|
37
|
+
const activeSession = sessions?.find(s => s.id === activeSessionId);
|
|
38
|
+
const activeTitle = activeSession ? sessionTitle(activeSession) : null;
|
|
39
|
+
|
|
40
|
+
// Session switcher dropdown state
|
|
41
|
+
const [switcherOpen, setSwitcherOpen] = useState(false);
|
|
42
|
+
const switcherRef = useRef<HTMLButtonElement>(null);
|
|
43
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
44
|
+
|
|
45
|
+
// Inline rename state
|
|
46
|
+
const [renamingId, setRenamingId] = useState<string | null>(null);
|
|
47
|
+
const [renameValue, setRenameValue] = useState('');
|
|
48
|
+
const renameInputRef = useRef<HTMLInputElement>(null);
|
|
49
|
+
|
|
50
|
+
// Close on outside click
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!switcherOpen) return;
|
|
53
|
+
const handler = (e: MouseEvent) => {
|
|
54
|
+
const target = e.target as Node;
|
|
55
|
+
if (
|
|
56
|
+
switcherRef.current && !switcherRef.current.contains(target) &&
|
|
57
|
+
dropdownRef.current && !dropdownRef.current.contains(target)
|
|
58
|
+
) {
|
|
59
|
+
setSwitcherOpen(false);
|
|
60
|
+
setRenamingId(null);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
document.addEventListener('mousedown', handler);
|
|
64
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
65
|
+
}, [switcherOpen]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!switcherOpen) return;
|
|
69
|
+
const handler = (e: KeyboardEvent) => {
|
|
70
|
+
if (e.key === 'Escape') {
|
|
71
|
+
if (renamingId) { setRenamingId(null); } else { setSwitcherOpen(false); }
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
document.addEventListener('keydown', handler);
|
|
75
|
+
return () => document.removeEventListener('keydown', handler);
|
|
76
|
+
}, [switcherOpen, renamingId]);
|
|
77
|
+
|
|
78
|
+
// Focus rename input when it appears
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (renamingId) setTimeout(() => renameInputRef.current?.focus(), 0);
|
|
81
|
+
}, [renamingId]);
|
|
82
|
+
|
|
83
|
+
const handleSelectSession = useCallback((id: string) => {
|
|
84
|
+
onLoadSession?.(id);
|
|
85
|
+
setSwitcherOpen(false);
|
|
86
|
+
}, [onLoadSession]);
|
|
87
|
+
|
|
88
|
+
const handleStartRename = useCallback((id: string, currentTitle: string) => {
|
|
89
|
+
setRenamingId(id);
|
|
90
|
+
setRenameValue(currentTitle === '(empty session)' ? '' : currentTitle);
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const handleCommitRename = useCallback(() => {
|
|
94
|
+
if (renamingId && onRenameSession && renameValue.trim()) {
|
|
95
|
+
onRenameSession(renamingId, renameValue.trim());
|
|
96
|
+
}
|
|
97
|
+
setRenamingId(null);
|
|
98
|
+
}, [renamingId, renameValue, onRenameSession]);
|
|
99
|
+
|
|
100
|
+
// Position dropdown below trigger
|
|
101
|
+
const [dropPos, setDropPos] = useState<{ top: number; left: number; width: number } | null>(null);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!switcherOpen || !switcherRef.current) return;
|
|
104
|
+
const rect = switcherRef.current.getBoundingClientRect();
|
|
105
|
+
setDropPos({ top: rect.bottom + 4, left: rect.left, width: Math.max(rect.width, 240) });
|
|
106
|
+
}, [switcherOpen]);
|
|
107
|
+
|
|
108
|
+
const switcherDropdown = switcherOpen && dropPos && sessions ? createPortal(
|
|
109
|
+
<div
|
|
110
|
+
ref={dropdownRef}
|
|
111
|
+
className="fixed z-50 rounded-xl border border-border/50 bg-card shadow-lg py-1 animate-in fade-in-0 slide-in-from-top-1 duration-100"
|
|
112
|
+
style={{ top: dropPos.top, left: dropPos.left, minWidth: dropPos.width, maxWidth: 320 }}
|
|
113
|
+
role="listbox"
|
|
114
|
+
>
|
|
115
|
+
{sessions.map((s) => {
|
|
116
|
+
const isActive = s.id === activeSessionId;
|
|
117
|
+
const title = sessionTitle(s);
|
|
118
|
+
const displayTitle = title === '(empty session)' ? (t.hints?.newChat ?? 'New chat') : title;
|
|
119
|
+
|
|
120
|
+
if (renamingId === s.id) {
|
|
121
|
+
return (
|
|
122
|
+
<div key={s.id} className="flex items-center gap-1 px-2 py-1.5">
|
|
123
|
+
<input
|
|
124
|
+
ref={renameInputRef}
|
|
125
|
+
type="text"
|
|
126
|
+
value={renameValue}
|
|
127
|
+
onChange={(e) => setRenameValue(e.target.value)}
|
|
128
|
+
onKeyDown={(e) => {
|
|
129
|
+
if (e.key === 'Enter') handleCommitRename();
|
|
130
|
+
if (e.key === 'Escape') setRenamingId(null);
|
|
131
|
+
}}
|
|
132
|
+
onBlur={handleCommitRename}
|
|
133
|
+
className="flex-1 min-w-0 px-2 py-1 text-xs rounded-md border border-border bg-background text-foreground outline-none focus:border-[var(--amber)]/50"
|
|
134
|
+
placeholder="Session name..."
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div key={s.id} className="group/item flex items-center">
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
role="option"
|
|
145
|
+
aria-selected={isActive}
|
|
146
|
+
onClick={() => handleSelectSession(s.id)}
|
|
147
|
+
className={`flex-1 min-w-0 flex items-center gap-2 px-3 py-2 text-xs text-left transition-colors ${
|
|
148
|
+
isActive ? 'text-foreground font-medium' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
|
149
|
+
}`}
|
|
150
|
+
>
|
|
151
|
+
{s.pinned && <Pin size={10} className="shrink-0 text-[var(--amber)]/60 -rotate-45" />}
|
|
152
|
+
{isActive && !s.pinned && <Check size={11} className="shrink-0 text-[var(--amber)]" />}
|
|
153
|
+
<span className="truncate">{displayTitle}</span>
|
|
154
|
+
</button>
|
|
155
|
+
<div className="shrink-0 flex items-center gap-0.5 mr-1 opacity-0 group-hover/item:opacity-100 transition-opacity">
|
|
156
|
+
{onTogglePinSession && (
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={(e) => { e.stopPropagation(); onTogglePinSession(s.id); }}
|
|
160
|
+
className={`p-1.5 rounded-md transition-colors ${s.pinned ? 'text-[var(--amber)] hover:text-muted-foreground hover:bg-muted/60' : 'text-muted-foreground/40 hover:text-[var(--amber)] hover:bg-[var(--amber)]/5'}`}
|
|
161
|
+
aria-label={s.pinned ? 'Unpin' : 'Pin'}
|
|
162
|
+
>
|
|
163
|
+
{s.pinned ? <PinOff size={10} /> : <Pin size={10} />}
|
|
164
|
+
</button>
|
|
165
|
+
)}
|
|
166
|
+
{onRenameSession && (
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
onClick={(e) => { e.stopPropagation(); handleStartRename(s.id, title); }}
|
|
170
|
+
className="p-1.5 rounded-md text-muted-foreground/40 hover:text-foreground hover:bg-muted/60"
|
|
171
|
+
aria-label={`Rename: ${displayTitle}`}
|
|
172
|
+
>
|
|
173
|
+
<Pencil size={10} />
|
|
174
|
+
</button>
|
|
175
|
+
)}
|
|
176
|
+
{sessions.length > 1 && onDeleteSession && (
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
onClick={(e) => { e.stopPropagation(); onDeleteSession(s.id); }}
|
|
180
|
+
className="p-1.5 rounded-md text-muted-foreground/40 hover:text-error hover:bg-error/5"
|
|
181
|
+
aria-label={`Delete: ${displayTitle}`}
|
|
182
|
+
>
|
|
183
|
+
<Trash2 size={10} />
|
|
184
|
+
</button>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
})}
|
|
190
|
+
</div>,
|
|
191
|
+
document.body,
|
|
192
|
+
) : null;
|
|
24
193
|
|
|
25
194
|
return (
|
|
26
|
-
<div className="flex items-center justify-between px-4 py-
|
|
195
|
+
<div className="flex items-center justify-between px-4 py-2.5 shrink-0">
|
|
27
196
|
{!isPanel && (
|
|
28
197
|
<div className="absolute top-2 left-1/2 -translate-x-1/2 w-8 h-1 rounded-full bg-muted-foreground/20 md:hidden" />
|
|
29
198
|
)}
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
199
|
+
{!hideTitle && (
|
|
200
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
201
|
+
<div className="w-6 h-6 rounded-lg bg-[var(--amber)]/10 flex items-center justify-center shrink-0">
|
|
202
|
+
<Sparkles size={13} className="text-[var(--amber)]" />
|
|
203
|
+
</div>
|
|
204
|
+
{hasMultipleSessions && activeTitle ? (
|
|
205
|
+
<button
|
|
206
|
+
ref={switcherRef}
|
|
207
|
+
type="button"
|
|
208
|
+
onClick={() => {
|
|
209
|
+
if (sessions && sessions.length >= 2) {
|
|
210
|
+
setSwitcherOpen(v => !v);
|
|
211
|
+
} else {
|
|
212
|
+
onToggleHistory();
|
|
213
|
+
}
|
|
214
|
+
}}
|
|
215
|
+
className="flex items-center gap-1 min-w-0 text-sm font-medium text-[var(--amber)] hover:text-[var(--amber)]/80 transition-colors"
|
|
216
|
+
aria-expanded={switcherOpen}
|
|
217
|
+
aria-haspopup="listbox"
|
|
218
|
+
>
|
|
219
|
+
<span className="truncate max-w-[180px]">{activeTitle === '(empty session)' ? (t.hints?.newChat ?? 'New chat') : activeTitle}</span>
|
|
220
|
+
<ChevronDown size={12} className={`shrink-0 text-muted-foreground transition-transform duration-150 ${switcherOpen ? 'rotate-180' : ''}`} />
|
|
221
|
+
</button>
|
|
222
|
+
) : activeTitle ? (
|
|
223
|
+
<span className="text-sm font-medium text-muted-foreground/60 truncate max-w-[180px]">
|
|
224
|
+
{activeTitle === '(empty session)' ? (t.hints?.newChat ?? 'New chat') : activeTitle}
|
|
225
|
+
</span>
|
|
226
|
+
) : (
|
|
227
|
+
/* Placeholder while sessions load — avoids flash of "MindOS Agent" text */
|
|
228
|
+
<span className="text-sm font-medium text-muted-foreground/40">
|
|
229
|
+
{t.hints?.newChat ?? 'New chat'}
|
|
230
|
+
</span>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
{hideTitle && <div />}
|
|
36
235
|
<div className="flex items-center gap-1 shrink-0">
|
|
37
|
-
<button type="button" onClick={(e) => { e.stopPropagation(); onToggleHistory(); }} aria-pressed={showHistory} className={`p-2 rounded transition-colors ${showHistory ? 'bg-
|
|
236
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onToggleHistory(); }} aria-pressed={showHistory} className={`p-2 rounded-lg transition-colors ${showHistory ? 'bg-[var(--amber)]/10 text-[var(--amber)]' : 'text-muted-foreground hover:text-foreground hover:bg-muted'}`} title={t.hints.sessionHistory}>
|
|
38
237
|
<History size={iconSize} />
|
|
39
238
|
</button>
|
|
40
|
-
<button type="button" onClick={(e) => { e.stopPropagation(); onReset(); }} disabled={isLoading} className="p-2 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors disabled:opacity-40" title={t.hints.newSession}>
|
|
239
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onReset(); }} disabled={isLoading} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors disabled:opacity-40" title={t.hints.newSession}>
|
|
41
240
|
<SquarePen size={iconSize} />
|
|
42
241
|
</button>
|
|
43
|
-
{
|
|
44
|
-
<button type="button" onClick={(e) => { e.stopPropagation(); onMaximize(); }} className="p-2 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ? t.hints.restorePanel : t.hints.maximizePanel}>
|
|
242
|
+
{onMaximize && (
|
|
243
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onMaximize(); }} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ? t.hints.restorePanel : t.hints.maximizePanel}>
|
|
45
244
|
{maximized ? <Minimize2 size={iconSize} /> : <Maximize2 size={iconSize} />}
|
|
46
245
|
</button>
|
|
47
246
|
)}
|
|
48
247
|
{onModeSwitch && (
|
|
49
|
-
<button type="button" onClick={(e) => { e.stopPropagation(); onModeSwitch(); }} className="p-2 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? t.hints.dockToSide : t.hints.openAsPopup}>
|
|
248
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onModeSwitch(); }} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? t.hints.dockToSide : t.hints.openAsPopup}>
|
|
50
249
|
{askMode === 'popup' ? <PanelRight size={iconSize} /> : <AppWindow size={iconSize} />}
|
|
51
250
|
</button>
|
|
52
251
|
)}
|
|
53
252
|
{onClose && (
|
|
54
|
-
<button type="button" onClick={(e) => { e.stopPropagation(); onClose(); }} className="p-2 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={t.hints.closePanel} aria-label="Close">
|
|
55
|
-
<X size={
|
|
253
|
+
<button type="button" onClick={(e) => { e.stopPropagation(); onClose(); }} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={t.hints.closePanel} aria-label="Close">
|
|
254
|
+
<X size={iconSize} />
|
|
56
255
|
</button>
|
|
57
256
|
)}
|
|
58
257
|
</div>
|
|
258
|
+
{typeof document !== 'undefined' && switcherDropdown}
|
|
59
259
|
</div>
|
|
60
260
|
);
|
|
61
261
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useRef, useEffect, memo, useState, useCallback } from 'react';
|
|
4
|
-
import { Sparkles, Loader2, AlertCircle, Wrench, WifiOff, Zap, Copy, Check, ArrowDown } from 'lucide-react';
|
|
4
|
+
import { Sparkles, Loader2, AlertCircle, Wrench, WifiOff, Zap, Copy, Check, ArrowDown, FolderInput, Search, PenLine, Lightbulb } from 'lucide-react';
|
|
5
5
|
import ReactMarkdown from 'react-markdown';
|
|
6
6
|
import remarkGfm from 'remark-gfm';
|
|
7
7
|
import type { Message, ImagePart } from '@/lib/types';
|
|
@@ -12,7 +12,7 @@ import ThinkingBlock from './ThinkingBlock';
|
|
|
12
12
|
|
|
13
13
|
const SKILL_PREFIX_RE = /^Use the skill ([^:]+):\s*/;
|
|
14
14
|
|
|
15
|
-
function CopyMessageButton({ text }: { text: string }) {
|
|
15
|
+
function CopyMessageButton({ text, label }: { text: string; label?: string }) {
|
|
16
16
|
const [copied, setCopied] = useState(false);
|
|
17
17
|
const handleCopy = useCallback(() => {
|
|
18
18
|
copyToClipboard(text).then(ok => {
|
|
@@ -27,8 +27,8 @@ function CopyMessageButton({ text }: { text: string }) {
|
|
|
27
27
|
<button
|
|
28
28
|
type="button"
|
|
29
29
|
onClick={handleCopy}
|
|
30
|
-
className="absolute -bottom-1 right-1 p-1 rounded-md bg-card border border-border/60 shadow-sm text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-opacity"
|
|
31
|
-
title=
|
|
30
|
+
className="absolute -bottom-1 right-1 p-1 rounded-md bg-card border border-border/60 shadow-sm text-muted-foreground hover:text-foreground opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity"
|
|
31
|
+
title={label ?? 'Copy'}
|
|
32
32
|
>
|
|
33
33
|
{copied ? <Check size={11} className="text-success" /> : <Copy size={11} />}
|
|
34
34
|
</button>
|
|
@@ -62,7 +62,7 @@ function UserMessageContent({ content, skillName, images }: { content: string; s
|
|
|
62
62
|
)}
|
|
63
63
|
{/* Skill capsule + text */}
|
|
64
64
|
{resolved && (
|
|
65
|
-
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-
|
|
65
|
+
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-white/20 text-white/90 mr-1 align-middle">
|
|
66
66
|
<Zap size={10} className="shrink-0" />
|
|
67
67
|
{resolved}
|
|
68
68
|
</span>
|
|
@@ -82,16 +82,16 @@ function AssistantMessage({ content, isStreaming }: { content: string; isStreami
|
|
|
82
82
|
prose-h1:text-base prose-h2:text-[15px] prose-h3:text-sm
|
|
83
83
|
prose-ul:my-1.5 prose-li:my-0.5
|
|
84
84
|
prose-ol:my-1.5
|
|
85
|
-
prose-code:text-[0.8em] prose-code:bg-
|
|
86
|
-
prose-pre:bg-
|
|
87
|
-
prose-blockquote:border-l-[var(--amber)] prose-blockquote:text-muted-foreground
|
|
85
|
+
prose-code:text-[0.8em] prose-code:bg-muted/80 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:before:content-none prose-code:after:content-none prose-code:font-mono
|
|
86
|
+
prose-pre:bg-muted/60 prose-pre:text-foreground prose-pre:text-xs prose-pre:rounded-lg
|
|
87
|
+
prose-blockquote:border-l-[var(--amber)] prose-blockquote:text-muted-foreground prose-blockquote:not-italic
|
|
88
88
|
prose-a:text-[var(--amber)] prose-a:no-underline hover:prose-a:underline
|
|
89
89
|
prose-strong:text-foreground prose-strong:font-semibold
|
|
90
|
-
prose-table:text-xs prose-th:py-1 prose-td:py-1
|
|
90
|
+
prose-table:text-xs prose-th:py-1.5 prose-td:py-1
|
|
91
91
|
">
|
|
92
92
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{cleaned}</ReactMarkdown>
|
|
93
93
|
{isStreaming && (
|
|
94
|
-
<span className="inline-block w-1.5 h-3.5 bg-[var(--amber)] ml-0.5 align-middle animate-pulse rounded-
|
|
94
|
+
<span className="inline-block w-1.5 h-3.5 bg-[var(--amber)] ml-0.5 align-middle animate-pulse rounded-full" />
|
|
95
95
|
)}
|
|
96
96
|
</div>
|
|
97
97
|
);
|
|
@@ -129,7 +129,7 @@ function AssistantMessageWithParts({ message, isStreaming }: { message: Message;
|
|
|
129
129
|
return null;
|
|
130
130
|
})}
|
|
131
131
|
{showTrailingSpinner && (
|
|
132
|
-
<div className="flex items-center gap-2 py-1 mt-1">
|
|
132
|
+
<div className="flex items-center gap-2 py-1.5 mt-1.5">
|
|
133
133
|
<Loader2 size={12} className="animate-spin text-[var(--amber)]" />
|
|
134
134
|
<span className="text-xs text-muted-foreground animate-pulse">Executing tool…</span>
|
|
135
135
|
</div>
|
|
@@ -145,9 +145,9 @@ function StepCounter({ parts }: { parts: Message['parts'] }) {
|
|
|
145
145
|
const lastToolCall = toolCalls[toolCalls.length - 1];
|
|
146
146
|
const toolLabel = lastToolCall.type === 'tool-call' ? lastToolCall.toolName : '';
|
|
147
147
|
return (
|
|
148
|
-
<div className="flex items-center gap-1.5 mt-1.5 text-xs text-muted-foreground/
|
|
148
|
+
<div className="flex items-center gap-1.5 mt-2 pt-1.5 border-t border-border/15 text-xs text-muted-foreground/60">
|
|
149
149
|
<Wrench size={10} />
|
|
150
|
-
<span>Step {toolCalls.length}{toolLabel ? ` — ${toolLabel}` : ''}</span>
|
|
150
|
+
<span className="font-medium">Step {toolCalls.length}{toolLabel ? ` — ${toolLabel}` : ''}</span>
|
|
151
151
|
</div>
|
|
152
152
|
);
|
|
153
153
|
}
|
|
@@ -158,13 +158,14 @@ interface MessageListProps {
|
|
|
158
158
|
loadingPhase: 'connecting' | 'thinking' | 'streaming' | 'reconnecting';
|
|
159
159
|
emptyPrompt: string;
|
|
160
160
|
emptyHint?: string;
|
|
161
|
-
suggestions: readonly string[];
|
|
161
|
+
suggestions: readonly { label: string; prompt: string }[];
|
|
162
162
|
onSuggestionClick: (text: string) => void;
|
|
163
163
|
labels: {
|
|
164
164
|
connecting: string;
|
|
165
165
|
thinking: string;
|
|
166
166
|
generating: string;
|
|
167
167
|
reconnecting?: string;
|
|
168
|
+
copyMessage?: string;
|
|
168
169
|
};
|
|
169
170
|
}
|
|
170
171
|
|
|
@@ -183,7 +184,9 @@ export default memo(function MessageList({
|
|
|
183
184
|
const [showScrollDown, setShowScrollDown] = useState(false);
|
|
184
185
|
|
|
185
186
|
const scrollToBottom = useCallback(() => {
|
|
186
|
-
|
|
187
|
+
const container = scrollContainerRef.current;
|
|
188
|
+
if (!container) return;
|
|
189
|
+
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
|
|
187
190
|
}, []);
|
|
188
191
|
|
|
189
192
|
useEffect(() => {
|
|
@@ -202,56 +205,65 @@ export default memo(function MessageList({
|
|
|
202
205
|
}, []);
|
|
203
206
|
|
|
204
207
|
return (
|
|
205
|
-
<div ref={scrollContainerRef} className="relative flex-1 overflow-y-auto overflow-x-hidden px-4 py-
|
|
208
|
+
<div ref={scrollContainerRef} role="log" aria-live="polite" className="relative flex-1 overflow-y-auto overflow-x-hidden px-4 py-5 space-y-5 min-h-0">
|
|
206
209
|
{messages.length === 0 && (
|
|
207
|
-
<div className="flex flex-col items-center justify-center flex-1 min-h-[
|
|
208
|
-
{/* Brand anchor */}
|
|
209
|
-
<div className="w-
|
|
210
|
-
<
|
|
210
|
+
<div className="flex flex-col items-center justify-center flex-1 min-h-[260px] px-6 pt-10 pb-4">
|
|
211
|
+
{/* Brand anchor — refined presence */}
|
|
212
|
+
<div className="relative w-12 h-12 rounded-2xl bg-[var(--amber)]/10 flex items-center justify-center mb-6">
|
|
213
|
+
<div className="absolute inset-0 rounded-2xl bg-[var(--amber)]/5 scale-[1.4]" />
|
|
214
|
+
<Sparkles size={22} className="text-[var(--amber)] relative z-10" />
|
|
211
215
|
</div>
|
|
212
|
-
<p className="text-center text-
|
|
216
|
+
<p className="text-center text-[15px] font-semibold text-foreground tracking-tight mb-2">{emptyPrompt}</p>
|
|
213
217
|
{emptyHint && (
|
|
214
|
-
<p className="text-center text-
|
|
218
|
+
<p className="text-center text-xs text-muted-foreground/80 mb-10 tracking-wide">{emptyHint}</p>
|
|
215
219
|
)}
|
|
216
|
-
{/* Suggestion chips —
|
|
217
|
-
<div className="
|
|
218
|
-
{suggestions.map((s, i) =>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
{/* Suggestion chips — refined single column */}
|
|
221
|
+
<div className="flex flex-col gap-2.5 max-w-[280px] w-full">
|
|
222
|
+
{suggestions.map((s, i) => {
|
|
223
|
+
const icons = [FolderInput, Search, PenLine, Lightbulb];
|
|
224
|
+
const SugIcon = icons[i % icons.length];
|
|
225
|
+
return (
|
|
226
|
+
<button
|
|
227
|
+
key={i}
|
|
228
|
+
type="button"
|
|
229
|
+
onClick={() => onSuggestionClick(s.prompt)}
|
|
230
|
+
className="group/sug flex items-center gap-3 text-left text-[13px] px-3.5 py-3 rounded-xl border border-border/40 bg-transparent text-muted-foreground hover:text-foreground hover:border-[var(--amber)]/30 hover:bg-[var(--amber)]/5 transition-all leading-snug"
|
|
231
|
+
aria-label={s.prompt}
|
|
232
|
+
>
|
|
233
|
+
<span className="shrink-0 w-8 h-8 rounded-lg bg-muted/60 flex items-center justify-center group-hover/sug:bg-[var(--amber)]/10 transition-colors">
|
|
234
|
+
<SugIcon size={15} className="text-muted-foreground/70 group-hover/sug:text-[var(--amber)] transition-colors" />
|
|
235
|
+
</span>
|
|
236
|
+
<span className="flex-1">{s.label}</span>
|
|
237
|
+
</button>
|
|
238
|
+
);
|
|
239
|
+
})}
|
|
228
240
|
</div>
|
|
229
241
|
</div>
|
|
230
242
|
)}
|
|
231
243
|
{messages.map((m, i) => (
|
|
232
|
-
<div key={i} className={`flex gap-3 ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
|
244
|
+
<div key={i} className={`flex gap-3 animate-[fadeSlideUp_0.22s_ease_both] ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
|
233
245
|
{m.role === 'assistant' && (
|
|
234
246
|
<div
|
|
235
|
-
className="w-
|
|
247
|
+
className="w-7 h-7 rounded-lg flex items-center justify-center shrink-0 mt-0.5 bg-[var(--amber)]/8"
|
|
236
248
|
>
|
|
237
|
-
<Sparkles size={
|
|
249
|
+
<Sparkles size={13} className="text-[var(--amber)]" />
|
|
238
250
|
</div>
|
|
239
251
|
)}
|
|
240
252
|
{m.role === 'user' ? (
|
|
241
253
|
<div
|
|
242
|
-
className="max-w-[85%] px-3 py-2 rounded-
|
|
254
|
+
className="max-w-[85%] px-3.5 py-2.5 rounded-2xl rounded-br-lg text-sm leading-relaxed whitespace-pre-wrap bg-[var(--amber)] text-[var(--amber-foreground)] shadow-sm shadow-[var(--amber)]/10"
|
|
243
255
|
>
|
|
244
256
|
<UserMessageContent content={m.content} skillName={m.skillName} images={m.images} />
|
|
245
257
|
</div>
|
|
246
258
|
) : m.content.startsWith('__error__') ? (
|
|
247
|
-
<div className="max-w-[85%] px-3 py-
|
|
248
|
-
<div className="flex items-start gap-2 text-error">
|
|
249
|
-
<AlertCircle size={
|
|
250
|
-
<span className="leading-relaxed">{m.content.slice(9)}</span>
|
|
259
|
+
<div className="max-w-[85%] px-3.5 py-3 rounded-2xl rounded-bl-md border border-error/30 bg-error/10 text-sm shadow-sm">
|
|
260
|
+
<div className="flex items-start gap-2.5 text-error">
|
|
261
|
+
<AlertCircle size={15} className="shrink-0 mt-0.5" />
|
|
262
|
+
<span className="leading-relaxed font-medium">{m.content.slice(9)}</span>
|
|
251
263
|
</div>
|
|
252
264
|
</div>
|
|
253
265
|
) : (
|
|
254
|
-
<div className="group relative max-w-[85%] px-3 py-2 rounded-
|
|
266
|
+
<div className="group relative max-w-[85%] px-3.5 py-2.5 rounded-2xl rounded-bl-lg bg-card border border-border/30 shadow-sm text-foreground text-sm">
|
|
255
267
|
{(m.parts && m.parts.length > 0) || stripThinkingTags(m.content) ? (
|
|
256
268
|
<>
|
|
257
269
|
<AssistantMessageWithParts message={m} isStreaming={isLoading && i === messages.length - 1} />
|
|
@@ -259,17 +271,18 @@ export default memo(function MessageList({
|
|
|
259
271
|
<StepCounter parts={m.parts} />
|
|
260
272
|
)}
|
|
261
273
|
{!(isLoading && i === messages.length - 1) && stripThinkingTags(m.content) && (
|
|
262
|
-
<CopyMessageButton text={stripThinkingTags(m.content)} />
|
|
274
|
+
<CopyMessageButton text={stripThinkingTags(m.content)} label={labels.copyMessage} />
|
|
263
275
|
)}
|
|
264
276
|
</>
|
|
265
277
|
) : isLoading && i === messages.length - 1 ? (
|
|
266
|
-
<div className="flex items-center gap-2 py-1">
|
|
278
|
+
<div className="flex items-center gap-2.5 py-1">
|
|
267
279
|
{loadingPhase === 'reconnecting' ? (
|
|
268
280
|
<WifiOff size={14} className="text-[var(--amber)] animate-pulse" />
|
|
269
281
|
) : (
|
|
270
282
|
<Loader2 size={14} className="animate-spin text-[var(--amber)]" />
|
|
271
283
|
)}
|
|
272
|
-
<span className="text-xs text-muted-foreground
|
|
284
|
+
<span className="text-xs text-muted-foreground">
|
|
285
|
+
<span className="inline-flex items-center gap-1">
|
|
273
286
|
{loadingPhase === 'reconnecting'
|
|
274
287
|
? (labels.reconnecting ?? 'Reconnecting...')
|
|
275
288
|
: loadingPhase === 'connecting'
|
|
@@ -277,6 +290,12 @@ export default memo(function MessageList({
|
|
|
277
290
|
: loadingPhase === 'thinking'
|
|
278
291
|
? labels.thinking
|
|
279
292
|
: labels.generating}
|
|
293
|
+
<span className="inline-flex gap-0.5">
|
|
294
|
+
<span className="w-1 h-1 rounded-full bg-[var(--amber)] animate-bounce [animation-delay:0ms]"></span>
|
|
295
|
+
<span className="w-1 h-1 rounded-full bg-[var(--amber)] animate-bounce [animation-delay:150ms]"></span>
|
|
296
|
+
<span className="w-1 h-1 rounded-full bg-[var(--amber)] animate-bounce [animation-delay:300ms]"></span>
|
|
297
|
+
</span>
|
|
298
|
+
</span>
|
|
280
299
|
</span>
|
|
281
300
|
</div>
|
|
282
301
|
) : null}
|
|
@@ -291,7 +310,7 @@ export default memo(function MessageList({
|
|
|
291
310
|
<button
|
|
292
311
|
type="button"
|
|
293
312
|
onClick={scrollToBottom}
|
|
294
|
-
className="sticky bottom-2 left-1/2 -translate-x-1/2 z-10 p-
|
|
313
|
+
className="sticky bottom-2 left-1/2 -translate-x-1/2 z-10 p-2 rounded-full border border-border/60 bg-card shadow-md text-muted-foreground hover:text-foreground hover:bg-muted transition-all hover:shadow-lg"
|
|
295
314
|
title="Scroll to bottom"
|
|
296
315
|
>
|
|
297
316
|
<ArrowDown size={14} />
|