@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.
Files changed (219) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +24 -24
  4. package/_standalone/.next/build-manifest.json +3 -3
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/react-loadable-manifest.json +1 -1
  10. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/_standalone/.next/server/app/_global-error.html +2 -2
  13. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  21. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  25. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js +2 -2
  27. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  28. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/ask/route.js +3 -3
  42. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  43. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  48. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  57. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  60. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  71. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  76. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/changes/page.js +2 -2
  87. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  88. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/echo/[segment]/page.js +3 -3
  90. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  91. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/echo/page.js +1 -1
  93. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  94. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/explore/page.js +2 -2
  96. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  97. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/help/page.js +2 -2
  99. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  100. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  102. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  103. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  104. package/_standalone/.next/server/app/login/page.js +2 -2
  105. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  106. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  107. package/_standalone/.next/server/app/page.js +2 -8
  108. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  109. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/setup/page.js +2 -2
  111. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  112. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  113. package/_standalone/.next/server/app/trash/page.js +3 -3
  114. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  115. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  116. package/_standalone/.next/server/app/view/[...path]/page.js +3 -3
  117. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  118. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  119. package/_standalone/.next/server/app/wiki/page.js +2 -2
  120. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
  121. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
  122. package/_standalone/.next/server/app-paths-manifest.json +24 -24
  123. package/_standalone/.next/server/chunks/3484.js +1 -1
  124. package/_standalone/.next/server/chunks/530.js +65 -66
  125. package/_standalone/.next/server/chunks/{6793.js → 8343.js} +2 -2
  126. package/_standalone/.next/server/chunks/9787.js +2 -0
  127. package/_standalone/.next/server/middleware-build-manifest.js +1 -1
  128. package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  129. package/_standalone/.next/server/pages/500.html +2 -2
  130. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  131. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  132. package/_standalone/.next/static/chunks/{1814.a7c127b2c73d1f70.js → 1814.a79b84d37df75c43.js} +1 -1
  133. package/_standalone/.next/static/chunks/3427-2e61a5df1f5e55fb.js +1 -0
  134. package/_standalone/.next/static/chunks/{1053-fe009233cff06e72.js → 5581-dac72e9f16e5ea29.js} +3 -3
  135. package/_standalone/.next/static/chunks/6297-085daa21037d5f81.js +1 -0
  136. package/_standalone/.next/static/chunks/{7249-fa98ca10e9a10f39.js → 7249-6cf8f2b78718c59e.js} +1 -1
  137. package/_standalone/.next/static/chunks/8520-56ec9ff087c15204.js +22 -0
  138. package/_standalone/.next/static/chunks/9905-a19d379cb225246e.js +1 -0
  139. package/_standalone/.next/static/chunks/app/agents/[agentKey]/{page-7bdeab5af8e4f5f2.js → page-35ea6de1af2be3b5.js} +1 -1
  140. package/_standalone/.next/static/chunks/app/agents/page-b172ea3743adb047.js +1 -0
  141. package/_standalone/.next/static/chunks/app/changes/{page-5a72144d1080a699.js → page-6d2f49651c0061f7.js} +1 -1
  142. package/_standalone/.next/static/chunks/app/echo/[segment]/page-84b95256f6e38aae.js +11 -0
  143. package/_standalone/.next/static/chunks/app/explore/{page-d3d99308146c2240.js → page-d9f58000bc445360.js} +2 -2
  144. package/_standalone/.next/static/chunks/app/help/{page-222df603080b5fab.js → page-f8cb806371b3175f.js} +1 -1
  145. package/_standalone/.next/static/chunks/app/inbox/history/{page-07819cf95cb0805f.js → page-26e71fb6f716a4c4.js} +1 -1
  146. package/_standalone/.next/static/chunks/app/layout-b89b0d955f39a753.js +164 -0
  147. package/_standalone/.next/static/chunks/app/login/{page-0eeef685052869a6.js → page-18fb00d568cd1f0e.js} +1 -1
  148. package/_standalone/.next/static/chunks/app/page-a8e6f085f38388bf.js +1 -0
  149. package/_standalone/.next/static/chunks/app/setup/{page-99fcfc460fa29733.js → page-821714e7477be46c.js} +1 -1
  150. package/_standalone/.next/static/chunks/app/trash/{page-54cbd5c98d9de69b.js → page-f92b728b78ac0f7e.js} +1 -1
  151. package/_standalone/.next/static/chunks/app/view/[...path]/{not-found-fc04c2bd4f35bc6f.js → not-found-6e0c75ad26ce8572.js} +1 -1
  152. package/_standalone/.next/static/chunks/app/view/[...path]/page-f87f4901b5e1a88f.js +12 -0
  153. package/_standalone/.next/static/chunks/app/wiki/page-641edb1f3cff2f93.js +1 -0
  154. package/_standalone/.next/static/chunks/{webpack-2c19436659aa657b.js → webpack-72e8d9e9073fd1f9.js} +1 -1
  155. package/_standalone/.next/static/css/6c104b118d3bc9b7.css +1 -0
  156. package/_standalone/.next/trace +64 -64
  157. package/_standalone/app/globals.css +2 -1
  158. package/_standalone/components/AskFab.tsx +4 -4
  159. package/_standalone/components/AskModal.tsx +1 -1
  160. package/_standalone/components/GuideCard.tsx +101 -152
  161. package/_standalone/components/RightAskPanel.tsx +2 -2
  162. package/_standalone/components/ask/AskContent.tsx +90 -51
  163. package/_standalone/components/ask/AskHeader.tsx +218 -18
  164. package/_standalone/components/ask/MessageList.tsx +66 -47
  165. package/_standalone/components/ask/SessionHistory.tsx +86 -60
  166. package/_standalone/components/ask/SessionTabBar.tsx +29 -21
  167. package/_standalone/components/ask/ThinkingBlock.tsx +6 -5
  168. package/_standalone/components/ask/ToolCallBlock.tsx +10 -9
  169. package/_standalone/components/settings/SettingsContent.tsx +1 -1
  170. package/_standalone/data/skills/mindos/SKILL.md +67 -15
  171. package/_standalone/data/skills/mindos-zh/SKILL.md +67 -11
  172. package/_standalone/hooks/useAskSession.ts +23 -1
  173. package/_standalone/lib/stores/locale-store.ts +20 -6
  174. package/_standalone/tsconfig.tsbuildinfo +1 -1
  175. package/app/app/globals.css +2 -1
  176. package/app/app/layout.tsx +16 -4
  177. package/app/components/AskFab.tsx +4 -4
  178. package/app/components/AskModal.tsx +1 -1
  179. package/app/components/GuideCard.tsx +101 -152
  180. package/app/components/HomeContent.tsx +116 -575
  181. package/app/components/RightAskPanel.tsx +2 -2
  182. package/app/components/WikiHomeContent.tsx +151 -3
  183. package/app/components/ask/AskContent.tsx +90 -51
  184. package/app/components/ask/AskHeader.tsx +218 -18
  185. package/app/components/ask/MessageList.tsx +66 -47
  186. package/app/components/ask/SessionHistory.tsx +86 -60
  187. package/app/components/ask/SessionTabBar.tsx +29 -21
  188. package/app/components/ask/ThinkingBlock.tsx +6 -5
  189. package/app/components/ask/ToolCallBlock.tsx +10 -9
  190. package/app/components/settings/SettingsContent.tsx +1 -1
  191. package/app/data/skills/mindos/SKILL.md +67 -15
  192. package/app/data/skills/mindos-zh/SKILL.md +67 -11
  193. package/app/hooks/useAskSession.ts +23 -1
  194. package/app/lib/i18n/modules/ai-chat.ts +97 -10
  195. package/app/lib/i18n/modules/onboarding.ts +12 -12
  196. package/app/lib/stores/LocaleStoreInit.tsx +24 -1
  197. package/app/lib/stores/locale-store.ts +20 -6
  198. package/app/lib/types.ts +1 -0
  199. package/package.json +1 -1
  200. package/skills/mindos/SKILL.md +67 -15
  201. package/skills/mindos/references/knowledge-health.md +120 -0
  202. package/skills/mindos-max/SKILL.md +52 -5
  203. package/skills/mindos-max-zh/SKILL.md +55 -6
  204. package/skills/mindos-zh/SKILL.md +67 -11
  205. package/_standalone/.next/server/chunks/2364.js +0 -2
  206. package/_standalone/.next/server/chunks/357.js +0 -1
  207. package/_standalone/.next/static/chunks/178-105779afb62d36d9.js +0 -1
  208. package/_standalone/.next/static/chunks/2218-d54538000574ffef.js +0 -1
  209. package/_standalone/.next/static/chunks/2549-e63cf57fa927a41d.js +0 -1
  210. package/_standalone/.next/static/chunks/9274-296ab35f9f09e42e.js +0 -1
  211. package/_standalone/.next/static/chunks/app/agents/page-5d1446665ddb3801.js +0 -1
  212. package/_standalone/.next/static/chunks/app/echo/[segment]/page-b0103509ce34444b.js +0 -11
  213. package/_standalone/.next/static/chunks/app/layout-7e02ddf4144b01f1.js +0 -186
  214. package/_standalone/.next/static/chunks/app/page-6a6a12bd6d6812d0.js +0 -7
  215. package/_standalone/.next/static/chunks/app/view/[...path]/page-ca7bdcbf27f88a46.js +0 -12
  216. package/_standalone/.next/static/chunks/app/wiki/page-d492256a93f0b8bc.js +0 -1
  217. package/_standalone/.next/static/css/fd84c8316ead16eb.css +0 -1
  218. /package/_standalone/.next/static/{2ksXveDzEcnCMRIElDkLq → u8p6oIRTcr_ns-ElNZ9rl}/_buildManifest.js +0 -0
  219. /package/_standalone/.next/static/{2ksXveDzEcnCMRIElDkLq → u8p6oIRTcr_ns-ElNZ9rl}/_ssgManifest.js +0 -0
@@ -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="Copy"
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-[var(--amber)]/15 text-[var(--amber)] mr-1 align-middle">
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-background/60 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none
86
- prose-pre:bg-background/60 prose-pre:text-foreground prose-pre:text-xs
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-sm" />
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/70">
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
- endRef.current?.scrollIntoView({ behavior: 'smooth' });
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-4 space-y-4 min-h-0">
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-[200px] px-4">
208
- {/* Brand anchor */}
209
- <div className="w-10 h-10 rounded-xl bg-[var(--amber)]/10 flex items-center justify-center mb-4">
210
- <Sparkles size={20} className="text-[var(--amber)]" />
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-sm font-medium text-foreground/80 mb-1">{emptyPrompt}</p>
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-[11px] text-muted-foreground/50 mb-5">{emptyHint}</p>
218
+ <p className="text-center text-xs text-muted-foreground/80 mb-10 tracking-wide">{emptyHint}</p>
215
219
  )}
216
- {/* Suggestion chips — centered 2-column grid */}
217
- <div className="grid grid-cols-2 gap-2 max-w-sm w-full">
218
- {suggestions.map((s, i) => (
219
- <button
220
- key={i}
221
- type="button"
222
- onClick={() => onSuggestionClick(s)}
223
- className="text-left text-xs px-3 py-2.5 rounded-lg border border-border/60 bg-card text-muted-foreground hover:text-foreground hover:border-[var(--amber)]/30 hover:bg-[var(--amber)]/5 transition-colors leading-snug"
224
- >
225
- {s}
226
- </button>
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-6 h-6 rounded-full flex items-center justify-center shrink-0 mt-0.5 bg-[var(--amber-dim)]"
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={12} className="text-[var(--amber)]" />
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-xl rounded-br-sm text-sm leading-relaxed whitespace-pre-wrap bg-[var(--amber)] text-[var(--amber-foreground)]"
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-2.5 rounded-xl rounded-bl-sm border border-error/20 bg-error/8 text-sm">
248
- <div className="flex items-start gap-2 text-error">
249
- <AlertCircle size={14} className="shrink-0 mt-0.5" />
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-xl rounded-bl-sm bg-muted text-foreground text-sm">
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 animate-pulse">
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-1.5 rounded-full border border-border/60 bg-card shadow-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
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} />
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useRef, useEffect, useCallback } from 'react';
4
- import { Trash2, Pencil } from 'lucide-react';
4
+ import { Trash2, Pencil, Pin, PinOff } from 'lucide-react';
5
5
  import type { ChatSession } from '@/lib/types';
6
6
  import { sessionTitle } from '@/hooks/useAskSession';
7
7
 
@@ -11,11 +11,25 @@ interface SessionHistoryProps {
11
11
  onLoad: (id: string) => void;
12
12
  onDelete: (id: string) => void;
13
13
  onRename: (id: string, title: string) => void;
14
+ onTogglePin: (id: string) => void;
14
15
  onClearAll: () => void;
15
16
  labels: { title: string; clearAll: string; confirmClear: string; noSessions: string; rename: string };
16
17
  }
17
18
 
18
- export default function SessionHistory({ sessions, activeSessionId, onLoad, onDelete, onRename, onClearAll, labels }: SessionHistoryProps) {
19
+ function formatRelativeTime(date: Date): string {
20
+ const now = Date.now();
21
+ const diff = now - date.getTime();
22
+ const mins = Math.floor(diff / 60000);
23
+ if (mins < 1) return 'just now';
24
+ if (mins < 60) return `${mins}m ago`;
25
+ const hours = Math.floor(mins / 60);
26
+ if (hours < 24) return `${hours}h ago`;
27
+ const days = Math.floor(hours / 24);
28
+ if (days < 7) return `${days}d ago`;
29
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
30
+ }
31
+
32
+ export default function SessionHistory({ sessions, activeSessionId, onLoad, onDelete, onRename, onTogglePin, onClearAll, labels }: SessionHistoryProps) {
19
33
  const [confirmClearAll, setConfirmClearAll] = useState(false);
20
34
  const clearTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
21
35
  const [editingId, setEditingId] = useState<string | null>(null);
@@ -60,78 +74,90 @@ export default function SessionHistory({ sessions, activeSessionId, onLoad, onDe
60
74
  }, []);
61
75
 
62
76
  return (
63
- <div className="border-b border-border px-4 py-2.5 max-h-[190px] overflow-y-auto">
64
- <div className="flex items-center justify-between mb-2">
65
- <span className="text-xs text-muted-foreground">{labels.title}</span>
77
+ <div className="border-b border-border/40 px-4 py-3 max-h-[220px] overflow-y-auto">
78
+ <div className="flex items-center justify-between mb-2.5">
79
+ <span className="text-xs font-medium text-muted-foreground">{labels.title}</span>
66
80
  {sessions.length > 1 && (
67
81
  <button
68
82
  type="button"
69
83
  onClick={handleClearAll}
70
- className={`text-2xs px-1.5 py-0.5 rounded transition-colors ${
84
+ className={`text-2xs px-2 py-0.5 rounded-md transition-colors ${
71
85
  confirmClearAll
72
86
  ? 'bg-error/10 text-error font-medium'
73
- : 'text-muted-foreground hover:text-error hover:bg-muted'
87
+ : 'text-muted-foreground/60 hover:text-error hover:bg-muted'
74
88
  }`}
75
89
  >
76
90
  {confirmClearAll ? labels.confirmClear : labels.clearAll}
77
91
  </button>
78
92
  )}
79
93
  </div>
80
- <div className="flex flex-col gap-1.5">
94
+ <div className="flex flex-col gap-1">
81
95
  {sessions.length === 0 && (
82
- <div className="text-xs text-muted-foreground/70">{labels.noSessions}</div>
96
+ <div className="text-xs text-muted-foreground/50 py-2 text-center">{labels.noSessions}</div>
83
97
  )}
84
- {sessions.map((s) => (
85
- <div key={s.id} className="group flex items-center gap-1">
86
- <button
87
- type="button"
88
- onClick={() => onLoad(s.id)}
89
- onDoubleClick={() => startRename(s)}
90
- className={`flex-1 text-left px-2 py-1.5 rounded text-xs transition-colors min-w-0 ${
91
- activeSessionId === s.id
92
- ? 'bg-accent text-foreground'
93
- : 'text-muted-foreground hover:bg-muted hover:text-foreground'
94
- }`}
95
- >
96
- {editingId === s.id ? (
97
- <input
98
- ref={inputRef}
99
- type="text"
100
- value={editValue}
101
- onChange={(e) => setEditValue(e.target.value)}
102
- onBlur={commitRename}
103
- onKeyDown={(e) => {
104
- if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
105
- if (e.key === 'Escape') { e.preventDefault(); cancelRename(); }
106
- }}
107
- onClick={(e) => e.stopPropagation()}
108
- className="w-full bg-transparent border-b border-[var(--amber)] outline-none text-xs text-foreground"
109
- />
110
- ) : (
111
- <div className="truncate">{sessionTitle(s)}</div>
112
- )}
113
- {editingId !== s.id && (
114
- <div className="text-2xs opacity-60">{new Date(s.updatedAt).toLocaleString()}</div>
115
- )}
116
- </button>
117
- <button
118
- type="button"
119
- onClick={() => startRename(s)}
120
- className="p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted opacity-0 group-hover:opacity-100 transition-opacity"
121
- title={labels.rename}
122
- >
123
- <Pencil size={11} />
124
- </button>
125
- <button
126
- type="button"
127
- onClick={() => onDelete(s.id)}
128
- className="p-1 rounded text-muted-foreground hover:text-error hover:bg-muted opacity-0 group-hover:opacity-100 transition-opacity"
129
- title="Delete session"
130
- >
131
- <Trash2 size={12} />
132
- </button>
133
- </div>
134
- ))}
98
+ {sessions.map((s) => {
99
+ const isActive = activeSessionId === s.id;
100
+ return (
101
+ <div key={s.id} className="group flex items-center gap-0.5">
102
+ {s.pinned && <Pin size={10} className="shrink-0 text-[var(--amber)]/50 -rotate-45 ml-1" />}
103
+ <button
104
+ type="button"
105
+ onClick={() => onLoad(s.id)}
106
+ onDoubleClick={() => startRename(s)}
107
+ className={`flex-1 text-left px-2.5 py-2 rounded-lg text-xs transition-colors min-w-0 ${
108
+ isActive
109
+ ? 'bg-[var(--amber)]/8 text-foreground border border-[var(--amber)]/15'
110
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground border border-transparent'
111
+ }`}
112
+ >
113
+ {editingId === s.id ? (
114
+ <input
115
+ ref={inputRef}
116
+ type="text"
117
+ value={editValue}
118
+ onChange={(e) => setEditValue(e.target.value)}
119
+ onBlur={commitRename}
120
+ onKeyDown={(e) => {
121
+ if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
122
+ if (e.key === 'Escape') { e.preventDefault(); cancelRename(); }
123
+ }}
124
+ onClick={(e) => e.stopPropagation()}
125
+ className="w-full bg-transparent border-b border-[var(--amber)] outline-none text-xs text-foreground"
126
+ />
127
+ ) : (
128
+ <div className="truncate font-medium">{sessionTitle(s)}</div>
129
+ )}
130
+ {editingId !== s.id && (
131
+ <div className="text-2xs text-muted-foreground/50 mt-0.5">{formatRelativeTime(new Date(s.updatedAt))}</div>
132
+ )}
133
+ </button>
134
+ <button
135
+ type="button"
136
+ onClick={() => onTogglePin(s.id)}
137
+ className={`p-1.5 rounded-lg transition-opacity opacity-0 group-hover:opacity-100 ${s.pinned ? 'text-[var(--amber)] hover:text-muted-foreground' : 'text-muted-foreground hover:text-[var(--amber)]'}`}
138
+ title={s.pinned ? 'Unpin' : 'Pin'}
139
+ >
140
+ {s.pinned ? <PinOff size={11} /> : <Pin size={11} />}
141
+ </button>
142
+ <button
143
+ type="button"
144
+ onClick={() => startRename(s)}
145
+ className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted opacity-0 group-hover:opacity-100 transition-opacity"
146
+ title={labels.rename}
147
+ >
148
+ <Pencil size={11} />
149
+ </button>
150
+ <button
151
+ type="button"
152
+ onClick={() => onDelete(s.id)}
153
+ className="p-1.5 rounded-lg text-muted-foreground hover:text-error hover:bg-error/5 opacity-0 group-hover:opacity-100 transition-opacity"
154
+ title="Delete session"
155
+ >
156
+ <Trash2 size={11} />
157
+ </button>
158
+ </div>
159
+ );
160
+ })}
135
161
  </div>
136
162
  </div>
137
163
  );
@@ -19,48 +19,56 @@ export default function SessionTabBar({
19
19
  }: SessionTabBarProps) {
20
20
  const { t } = useLocale();
21
21
  const visibleSessions = sessions.slice(0, maxTabs);
22
+ const hasMore = sessions.length > maxTabs;
22
23
 
23
24
  if (visibleSessions.length === 0) return null;
24
25
 
25
26
  return (
26
- <div className="flex items-center border-b border-border shrink-0 bg-background/50">
27
+ <div className="flex items-center border-b border-border/60 shrink-0 bg-background/50" role="tablist">
27
28
  <div className="flex flex-1 min-w-0">
28
29
  {visibleSessions.map((s) => {
29
30
  const isActive = s.id === activeSessionId;
30
31
  const title = sessionTitle(s);
31
32
  return (
32
- <button
33
- key={s.id}
34
- type="button"
35
- onClick={() => onLoad(s.id)}
36
- className={`group relative flex items-center gap-1 min-w-0 max-w-[160px] px-3 py-2 text-xs transition-colors
37
- ${isActive
38
- ? 'text-foreground border-b-2 border-[var(--amber)] bg-card'
39
- : 'text-muted-foreground hover:text-foreground hover:bg-muted/50 border-b-2 border-transparent'
40
- }`}
41
- title={title}
42
- >
43
- <span className="truncate">{title === '(empty session)' ? t.hints.newChat : title}</span>
33
+ <div key={s.id} className="group relative flex items-center min-w-0 max-w-[160px]">
34
+ <button
35
+ type="button"
36
+ role="tab"
37
+ aria-selected={isActive}
38
+ onClick={() => onLoad(s.id)}
39
+ className={`flex-1 min-w-0 px-3 py-2.5 text-xs transition-colors truncate
40
+ ${isActive
41
+ ? 'text-foreground border-b-2 border-[var(--amber)] bg-card font-medium'
42
+ : 'text-muted-foreground hover:text-foreground hover:bg-muted/50 border-b-2 border-transparent'
43
+ }`}
44
+ title={title}
45
+ >
46
+ {title === '(empty session)' ? t.hints.newChat : title}
47
+ </button>
44
48
  {visibleSessions.length > 1 && (
45
- <span
46
- role="button"
47
- tabIndex={0}
49
+ <button
50
+ type="button"
48
51
  onClick={(e) => { e.stopPropagation(); onDelete(s.id); }}
49
- onKeyDown={(e) => { if (e.key === 'Enter') { e.stopPropagation(); onDelete(s.id); } }}
50
- className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 hover:bg-muted hover:text-error transition-opacity"
52
+ className="absolute right-0.5 top-1/2 -translate-y-1/2 shrink-0 p-0.5 rounded opacity-100 md:opacity-0 md:group-hover:opacity-100 hover:bg-muted hover:text-error transition-opacity"
51
53
  title={t.hints.closeSession}
54
+ aria-label={`${t.hints.closeSession}: ${title}`}
52
55
  >
53
56
  <X size={10} />
54
- </span>
57
+ </button>
55
58
  )}
56
- </button>
59
+ </div>
57
60
  );
58
61
  })}
62
+ {hasMore && (
63
+ <span className="flex items-center px-2 text-2xs text-muted-foreground/60">
64
+ +{sessions.length - maxTabs}
65
+ </span>
66
+ )}
59
67
  </div>
60
68
  <button
61
69
  type="button"
62
70
  onClick={onNew}
63
- className="shrink-0 p-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
71
+ className="shrink-0 p-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors rounded-lg"
64
72
  title={t.hints.newChat}
65
73
  >
66
74
  <Plus size={13} />
@@ -16,11 +16,12 @@ export default function ThinkingBlock({ text, isStreaming }: ThinkingBlockProps)
16
16
  if (!text && !isStreaming) return null;
17
17
 
18
18
  return (
19
- <div className="my-1 rounded-md border border-border/40 bg-muted/20 text-xs">
19
+ <div className="my-1.5 rounded-lg border border-border/30 bg-muted/15 text-xs">
20
20
  <button
21
21
  type="button"
22
22
  onClick={() => setExpanded(v => !v)}
23
- className="w-full flex items-center gap-1.5 px-2 py-1.5 text-left hover:bg-muted/30 transition-colors rounded-md"
23
+ aria-expanded={expanded}
24
+ className="w-full flex items-center gap-1.5 px-2.5 py-2 text-left hover:bg-muted/30 transition-colors rounded-lg"
24
25
  >
25
26
  {expanded ? (
26
27
  <ChevronDown size={12} className="shrink-0 text-muted-foreground" />
@@ -35,17 +36,17 @@ export default function ThinkingBlock({ text, isStreaming }: ThinkingBlockProps)
35
36
  )}
36
37
  </span>
37
38
  {!expanded && text && (
38
- <span className="text-muted-foreground/60 truncate flex-1 ml-1">
39
+ <span className="text-muted-foreground/50 truncate flex-1 ml-1">
39
40
  {text.slice(0, 80)}{text.length > 80 ? '...' : ''}
40
41
  </span>
41
42
  )}
42
43
  </button>
43
44
  {expanded && (
44
- <div className="px-2 pb-2 pt-0.5 border-t border-border/30">
45
+ <div className="px-2.5 pb-2.5 pt-1 border-t border-border/20">
45
46
  <div className="text-muted-foreground whitespace-pre-wrap leading-relaxed">
46
47
  {text}
47
48
  {isStreaming && (
48
- <span className="inline-block w-1 h-3 bg-muted-foreground/40 ml-0.5 align-middle animate-pulse rounded-sm" />
49
+ <span className="inline-block w-1 h-3 bg-muted-foreground/40 ml-0.5 align-middle animate-pulse rounded-full" />
49
50
  )}
50
51
  </div>
51
52
  </div>
@@ -101,15 +101,16 @@ export default function ToolCallBlock({ part }: { part: ToolCallPart }) {
101
101
  : formatInput(part.input);
102
102
 
103
103
  return (
104
- <div className={`my-1 rounded-md border text-xs font-mono ${
104
+ <div className={`my-1.5 rounded-lg border text-xs font-mono ${
105
105
  isDestructive
106
106
  ? 'border-[var(--amber)]/30 bg-background/60'
107
- : 'border-border/50 bg-background/60'
107
+ : 'border-border/40 bg-background/50'
108
108
  }`}>
109
109
  <button
110
110
  type="button"
111
111
  onClick={() => setManualToggle(v => v === null ? !expanded : !v)}
112
- className="w-full flex items-center gap-1.5 px-2 py-1.5 text-left hover:bg-muted/30 transition-colors rounded-md"
112
+ aria-expanded={expanded}
113
+ className="w-full flex items-center gap-1.5 px-2.5 py-2 text-left hover:bg-muted/30 transition-colors rounded-lg"
113
114
  >
114
115
  {expanded ? <ChevronDown size={12} className="shrink-0 text-muted-foreground" /> : <ChevronRight size={12} className="shrink-0 text-muted-foreground" />}
115
116
  {isDestructive && <AlertTriangle size={11} className="shrink-0 text-[var(--amber)]" />}
@@ -161,19 +162,19 @@ export default function ToolCallBlock({ part }: { part: ToolCallPart }) {
161
162
  </div>
162
163
  ) : (
163
164
  /* Fallback: show input (always), output when available */
164
- <div className="px-2 pb-2 pt-1 space-y-1">
165
+ <div className="px-2.5 pb-2.5 pt-1.5 space-y-1.5">
165
166
  {part.state === 'running' && (
166
- <div className="text-muted-foreground/60 text-2xs flex items-center gap-1">
167
+ <div className="text-muted-foreground/60 text-2xs flex items-center gap-1.5">
167
168
  <Loader2 size={10} className="animate-spin" /> Running...
168
169
  </div>
169
170
  )}
170
- <div className="text-muted-foreground">
171
- <span className="font-semibold">Input: </span>
171
+ <div className="text-muted-foreground leading-relaxed">
172
+ <span className="font-semibold text-foreground/70">Input: </span>
172
173
  <span className="break-all whitespace-pre-wrap">{JSON.stringify(part.input, null, 2)}</span>
173
174
  </div>
174
175
  {part.output !== undefined && part.output !== '' && (
175
- <div className="text-muted-foreground">
176
- <span className="font-semibold">Output: </span>
176
+ <div className="text-muted-foreground leading-relaxed">
177
+ <span className="font-semibold text-foreground/70">Output: </span>
177
178
  <span className="break-all whitespace-pre-wrap">{part.output.length > 500 ? part.output.slice(0, 500) + '…' : part.output}</span>
178
179
  </div>
179
180
  )}
@@ -66,7 +66,7 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
66
66
 
67
67
  if (justOpened) {
68
68
  apiFetch<SettingsData>('/api/settings').then(d => { setData(d); dataLoaded.current = true; }).catch(() => setStatus('load-error'));
69
- setFont(localStorage.getItem('prose-font') === 'geist' ? 'inter' : localStorage.getItem('prose-font') ?? 'lora');
69
+ setFont(localStorage.getItem('prose-font') === 'geist' ? 'inter' : localStorage.getItem('prose-font') ?? 'ibm-plex-sans');
70
70
  setFontSize(localStorage.getItem('prose-font-size') ?? '15px');
71
71
  setContentWidth(localStorage.getItem('content-width') ?? '780px');
72
72
  const stored = localStorage.getItem('theme');