@nextclaw/ui 0.15.20 → 0.15.22

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 (191) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/assets/{api-tWUa6l9J.js → api-B1yHQYs-.js} +6 -6
  3. package/dist/assets/app-presenter-provider-uP9waOzv.js +108 -0
  4. package/dist/assets/appearance-settings-page-C9Zq3hP9.js +1 -0
  5. package/dist/assets/{book-open-BQ29ZmDE.js → book-open-oSmyVXea.js} +1 -1
  6. package/dist/assets/{card-CtKueklV.js → card-CGvJZTZp.js} +1 -1
  7. package/dist/assets/{channels-list-page-C8y3rqYt.js → channels-list-page-5BTxpQHw.js} +2 -2
  8. package/dist/assets/chat-page-jTvli8Jp.js +1 -0
  9. package/dist/assets/config-split-page-BgBsUmp1.js +1 -0
  10. package/dist/assets/confirm-dialog-h849QRWO.js +5 -0
  11. package/dist/assets/desktop-update-config-BGGHxClR.js +1 -0
  12. package/dist/assets/{dist-rYHnBo8t.js → dist-CxUhurq9.js} +1 -1
  13. package/dist/assets/doc-browser-0eL8AX42.js +1 -0
  14. package/dist/assets/{doc-browser-D8ng4EUc.js → doc-browser-DEqE9lsh.js} +1 -1
  15. package/dist/assets/{doc-browser-context-CPHNzQ8m.js → doc-browser-context-DkzFIUoK.js} +1 -1
  16. package/dist/assets/{eye-CiJz3ODY.js → eye-gOWQGt8N.js} +1 -1
  17. package/dist/assets/{form-actions-BnZVzLZf.js → form-actions-CgjYZUzM.js} +1 -1
  18. package/dist/assets/index-DSAUWCfl.js +2 -0
  19. package/dist/assets/index-G7cBryJ4.css +1 -0
  20. package/dist/assets/loader-circle-BAuycGf6.js +1 -0
  21. package/dist/assets/{logo-badge-BXI-Kdhd.js → logo-badge-BNcsDsUM.js} +1 -1
  22. package/dist/assets/{mcp-marketplace-page-CxNe-Q9Q.js → mcp-marketplace-page-BX39MtPa.js} +3 -3
  23. package/dist/assets/mcp-marketplace-page-CkDtOEA4.js +1 -0
  24. package/dist/assets/{model-config-page-CR_mgEVp.js → model-config-page-7MIXg2fQ.js} +1 -1
  25. package/dist/assets/{navigation-link-C1cvB7y4.js → navigation-link-B3iFOdxx.js} +1 -1
  26. package/dist/assets/plus-yvhFgfal.js +1 -0
  27. package/dist/assets/{popover-ZoDwVwwd.js → popover-Cpep_rJe.js} +1 -1
  28. package/dist/assets/{provider-models-CXUgDxGR.js → provider-models-CUrptZ-o.js} +1 -1
  29. package/dist/assets/provider-scoped-model-input-CHmOzz7x.js +1 -0
  30. package/dist/assets/providers-config-page-A6J7Y0II.js +1 -0
  31. package/dist/assets/react-BCJGCjrP.js +8 -0
  32. package/dist/assets/{refresh-cw-DKewcdAl.js → refresh-cw-CkKwPztT.js} +1 -1
  33. package/dist/assets/remote-FD-ZfSIn.js +1 -0
  34. package/dist/assets/rotate-cw-DrJMfHhp.js +1 -0
  35. package/dist/assets/runtime-config-page-DwjO-Vs6.js +1 -0
  36. package/dist/assets/{save-BrckuAXY.js → save-BINMWate.js} +1 -1
  37. package/dist/assets/{search-BhXQB2TO.js → search-7M38azRp.js} +1 -1
  38. package/dist/assets/{search-config-page-DFOQ6SDJ.js → search-config-page-T6tMhXbP.js} +1 -1
  39. package/dist/assets/{secrets-config-page-BqqeClqE.js → secrets-config-page-2mVejXf2.js} +2 -2
  40. package/dist/assets/security-config-DD5RrjDe.js +1 -0
  41. package/dist/assets/{select-CVzGGT38.js → select-BvidP5Oa.js} +1 -1
  42. package/dist/assets/{settings-2-BP6L2enG.js → settings-2-DqZstREV.js} +1 -1
  43. package/dist/assets/skeleton-BEQW2Dva.js +1 -0
  44. package/dist/assets/{tabs-B-QWjKCL.js → tabs-BeLxVmkt.js} +1 -1
  45. package/dist/assets/{tag-chip-C88mxjwx.js → tag-chip-BoDaLnWt.js} +1 -1
  46. package/dist/assets/{trash-2-ck3t33I3.js → trash-2-CYUskMMN.js} +1 -1
  47. package/dist/assets/{use-config-BAr5bH2c.js → use-config-CxKRXXCl.js} +1 -1
  48. package/dist/assets/use-confirm-dialog-HASTvOP2.js +1 -0
  49. package/dist/assets/{use-viewport-layout-k1isXfNm.js → use-viewport-layout-CJxkjoRz.js} +1 -1
  50. package/dist/assets/x-DdUwjre5.js +1 -0
  51. package/dist/index.html +29 -29
  52. package/package.json +8 -8
  53. package/src/app/components/__tests__/app-notification-runtime.test.tsx +32 -0
  54. package/src/app/components/app-notification-runtime.tsx +15 -0
  55. package/src/app/components/layout/sidebar-items.tsx +23 -9
  56. package/src/app/components/layout/sidebar-rail.styles.ts +0 -2
  57. package/src/app/components/layout/sidebar.tsx +2 -2
  58. package/src/app/configs/app-navigation.config.ts +21 -0
  59. package/src/app/index.tsx +22 -2
  60. package/src/app/presenters/app.presenter.ts +8 -0
  61. package/src/app/styles/design-system.css +12 -0
  62. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +2 -2
  63. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +19 -1
  64. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +1 -1
  65. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -6
  66. package/src/features/chat/components/conversation/chat-conversation-header.tsx +1 -1
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +1 -1
  68. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +52 -0
  69. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +11 -17
  70. package/src/features/chat/components/layout/chat-page-shell.tsx +9 -13
  71. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +40 -38
  72. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +13 -64
  73. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -4
  74. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -1
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +15 -4
  76. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +7 -7
  77. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +4 -2
  78. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +1 -0
  79. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +1 -0
  80. package/src/features/chat/features/conversation/utils/session-queued-input.utils.ts +1 -0
  81. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +3 -0
  82. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +0 -1
  83. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +3 -0
  84. package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +29 -0
  85. package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +4 -1
  86. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +10 -10
  87. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  88. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  89. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +1 -1
  90. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +1 -1
  91. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +1 -1
  92. package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +26 -0
  93. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +4 -2
  94. package/src/features/chat/features/session/utils/chat-session-display.utils.ts +48 -12
  95. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +13 -4
  96. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +6 -0
  97. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +10 -7
  98. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +1 -1
  99. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -14
  100. package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +37 -0
  101. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
  102. package/src/features/chat/features/welcome/components/chat-welcome.tsx +33 -31
  103. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -1
  104. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -1
  105. package/src/features/chat/index.ts +1 -0
  106. package/src/features/chat/managers/__tests__/chat-completion-notification.manager.test.ts +177 -0
  107. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +15 -3
  108. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +3 -10
  109. package/src/features/chat/managers/chat-completion-notification.manager.ts +146 -0
  110. package/src/features/chat/managers/chat-session-list.manager.ts +3 -23
  111. package/src/features/chat/managers/chat-thread.manager.ts +2 -5
  112. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +32 -11
  113. package/src/features/chat/pages/ncp-chat-page.tsx +9 -1
  114. package/src/features/chat/types/chat-input-bar.types.ts +1 -0
  115. package/src/features/cron/components/cron-task-discovery.tsx +1 -1
  116. package/src/features/inbox/components/inbox-delivery-content.test.tsx +57 -0
  117. package/src/features/inbox/components/inbox-delivery-content.tsx +118 -0
  118. package/src/features/inbox/components/inbox-reader-dialog.test.tsx +77 -0
  119. package/src/features/inbox/components/inbox-reader-dialog.tsx +224 -0
  120. package/src/features/inbox/components/inbox-runtime.tsx +14 -0
  121. package/src/features/inbox/hooks/use-inbox-deliveries.ts +14 -0
  122. package/src/features/inbox/index.ts +7 -0
  123. package/src/features/inbox/managers/inbox.manager.test.ts +86 -0
  124. package/src/features/inbox/managers/inbox.manager.ts +178 -0
  125. package/src/features/inbox/pages/inbox-page.test.ts +28 -0
  126. package/src/features/inbox/pages/inbox-page.tsx +385 -0
  127. package/src/features/inbox/stores/inbox.store.ts +23 -0
  128. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  129. package/src/features/notifications/components/__tests__/app-notification-toast.test.tsx +51 -0
  130. package/src/features/notifications/components/app-notification-toast.tsx +80 -0
  131. package/src/features/notifications/index.ts +8 -0
  132. package/src/features/notifications/managers/__tests__/app-notification.manager.test.ts +75 -0
  133. package/src/features/notifications/managers/app-notification.manager.ts +41 -0
  134. package/src/features/panel-apps/components/panel-app-list-item.tsx +1 -1
  135. package/src/features/panel-apps/components/panel-app-toolbar.tsx +2 -2
  136. package/src/features/panel-apps/components/panel-apps-list.tsx +4 -4
  137. package/src/features/service-apps/components/service-apps-panel.tsx +1 -1
  138. package/src/features/settings/components/config/provider-auth-section.tsx +90 -59
  139. package/src/features/settings/components/config/provider-form.tsx +10 -17
  140. package/src/features/settings/components/config/provider-status-badge.tsx +4 -2
  141. package/src/features/settings/pages/__tests__/providers-config-page.test.tsx +12 -0
  142. package/src/features/settings/pages/providers-config-page.tsx +2 -1
  143. package/src/platforms/mobile/components/__tests__/mobile-app-shell.test.tsx +13 -7
  144. package/src/platforms/mobile/components/__tests__/mobile-bottom-nav.test.tsx +18 -20
  145. package/src/platforms/mobile/components/mobile-bottom-nav.tsx +14 -7
  146. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
  147. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +1 -1
  148. package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
  149. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +3 -3
  150. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +1 -1
  151. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  152. package/src/shared/components/ui/button.tsx +4 -4
  153. package/src/shared/components/ui/dialog.tsx +1 -1
  154. package/src/shared/components/ui/input.tsx +1 -1
  155. package/src/shared/components/ui/scroll-area.tsx +2 -0
  156. package/src/shared/components/ui/select.tsx +1 -1
  157. package/src/shared/components/ui/switch.tsx +1 -1
  158. package/src/shared/components/ui/tabs.tsx +1 -1
  159. package/src/shared/components/ui/textarea.tsx +1 -1
  160. package/src/shared/lib/api/ncp-session.types.ts +1 -0
  161. package/src/shared/lib/api/types.ts +2 -0
  162. package/src/shared/lib/i18n/index.ts +4 -0
  163. package/src/shared/lib/i18n/locales/en-US/chat.json +16 -14
  164. package/src/shared/lib/i18n/locales/en-US/inbox.json +30 -0
  165. package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -14
  166. package/src/shared/lib/i18n/locales/zh-CN/inbox.json +30 -0
  167. package/src/shared/lib/provider-models/__tests__/anonymous-provider-configuration.test.ts +40 -0
  168. package/src/shared/lib/provider-models/index.ts +4 -3
  169. package/dist/assets/app-presenter-provider-s-FrOinZ.js +0 -106
  170. package/dist/assets/appearance-settings-page-dMRBD7ni.js +0 -1
  171. package/dist/assets/chat-page-MXBltS3G.js +0 -1
  172. package/dist/assets/config-split-page-uC6d_r9c.js +0 -1
  173. package/dist/assets/confirm-dialog-C1pbqHZX.js +0 -5
  174. package/dist/assets/desktop-update-config-DssP8LCk.js +0 -1
  175. package/dist/assets/doc-browser-AXF1zRsM.js +0 -1
  176. package/dist/assets/index-CocHWEsI.js +0 -2
  177. package/dist/assets/index-DN6KM7WD.css +0 -1
  178. package/dist/assets/loader-circle-AOpK5kkW.js +0 -1
  179. package/dist/assets/mcp-marketplace-page-5ogeBHnl.js +0 -1
  180. package/dist/assets/plus-DI37QuHc.js +0 -1
  181. package/dist/assets/provider-scoped-model-input-DIL6Ex_d.js +0 -1
  182. package/dist/assets/providers-config-page-D2NXVctW.js +0 -1
  183. package/dist/assets/react-39CDNZlM.js +0 -8
  184. package/dist/assets/remote-DYOOO1YG.js +0 -1
  185. package/dist/assets/rotate-cw-PmqA8LQT.js +0 -1
  186. package/dist/assets/runtime-config-page-0lb5NyKc.js +0 -1
  187. package/dist/assets/security-config-13AtPIlm.js +0 -1
  188. package/dist/assets/skeleton-OwCNQJ7M.js +0 -1
  189. package/dist/assets/use-confirm-dialog-C4e0nG_4.js +0 -1
  190. package/dist/assets/x-C1ijNWcE.js +0 -1
  191. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +0 -59
@@ -1,4 +1,5 @@
1
1
  import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
3
  import { MemoryRouter } from "react-router-dom";
3
4
  import { describe, expect, it, vi } from "vitest";
4
5
  import { I18nProvider } from "@/app/components/i18n-provider";
@@ -11,15 +12,24 @@ vi.mock("@/shared/components/doc-browser", () => ({
11
12
  useDocBrowser: () => ({ open: vi.fn() }),
12
13
  }));
13
14
 
14
- describe("MobileBottomNav", () => {
15
- it("highlights the settings tab for nested settings routes", () => {
16
- render(
15
+ function renderBottomNav(pathname: string) {
16
+ const queryClient = new QueryClient({
17
+ defaultOptions: { queries: { retry: false } },
18
+ });
19
+ return render(
20
+ <QueryClientProvider client={queryClient}>
17
21
  <I18nProvider>
18
- <MemoryRouter initialEntries={["/providers"]}>
22
+ <MemoryRouter initialEntries={[pathname]}>
19
23
  <MobileBottomNav />
20
24
  </MemoryRouter>
21
- </I18nProvider>,
22
- );
25
+ </I18nProvider>
26
+ </QueryClientProvider>,
27
+ );
28
+ }
29
+
30
+ describe("MobileBottomNav", () => {
31
+ it("highlights the settings tab for nested settings routes", () => {
32
+ renderBottomNav("/providers");
23
33
 
24
34
  expect(
25
35
  screen.getByRole("link", { name: /settings/i }).getAttribute("aria-current"),
@@ -37,13 +47,7 @@ describe("MobileBottomNav", () => {
37
47
  });
38
48
 
39
49
  it("highlights the chat tab for chat routes", () => {
40
- render(
41
- <I18nProvider>
42
- <MemoryRouter initialEntries={["/chat/demo-session"]}>
43
- <MobileBottomNav />
44
- </MemoryRouter>
45
- </I18nProvider>,
46
- );
50
+ renderBottomNav("/chat/demo-session");
47
51
 
48
52
  expect(
49
53
  screen.getByRole("link", { name: /chat/i }).getAttribute("aria-current"),
@@ -51,13 +55,7 @@ describe("MobileBottomNav", () => {
51
55
  });
52
56
 
53
57
  it("opens the apps panel from the mobile navigation", () => {
54
- render(
55
- <I18nProvider>
56
- <MemoryRouter initialEntries={["/chat"]}>
57
- <MobileBottomNav />
58
- </MemoryRouter>
59
- </I18nProvider>,
60
- );
58
+ renderBottomNav("/chat");
61
59
 
62
60
  fireEvent.click(screen.getByRole("button", { name: "Apps" }));
63
61
 
@@ -6,6 +6,7 @@ import {
6
6
  matchesRouteTarget,
7
7
  } from "@/app/configs/app-navigation.config";
8
8
  import { openApps } from "@/features/panel-apps";
9
+ import { useInboxUnreadCount } from "@/features/inbox";
9
10
  import { useDocBrowser } from "@/shared/components/doc-browser";
10
11
  import { t } from "@/shared/lib/i18n";
11
12
  import { cn } from "@/shared/lib/utils";
@@ -13,6 +14,7 @@ import { cn } from "@/shared/lib/utils";
13
14
  export function MobileBottomNav() {
14
15
  const { pathname } = useLocation();
15
16
  const docBrowser = useDocBrowser();
17
+ const unreadCount = useInboxUnreadCount();
16
18
 
17
19
  return (
18
20
  <nav
@@ -23,7 +25,7 @@ export function MobileBottomNav() {
23
25
  paddingBottom: "calc(env(safe-area-inset-bottom, 0px) + 0.25rem)",
24
26
  }}
25
27
  >
26
- <ul className="grid grid-cols-5 gap-1 px-2 pt-1">
28
+ <ul className="grid grid-cols-6 gap-1 px-2 pt-1">
27
29
  {getMobileBottomNavItems(t).map((item) => {
28
30
  const active = item.target === "/settings"
29
31
  ? isSettingsRoute(pathname)
@@ -41,12 +43,17 @@ export function MobileBottomNav() {
41
43
  : "text-gray-500 hover:bg-gray-50 hover:text-gray-800",
42
44
  )}
43
45
  >
44
- <item.icon
45
- className={cn(
46
- "h-3.5 w-3.5",
47
- active ? "text-gray-900" : "text-gray-400",
48
- )}
49
- />
46
+ <span className="relative">
47
+ <item.icon
48
+ className={cn(
49
+ "h-3.5 w-3.5",
50
+ active ? "text-gray-900" : "text-gray-400",
51
+ )}
52
+ />
53
+ {item.target === "/inbox" && unreadCount > 0 ? (
54
+ <span className="absolute -right-1 -top-1 h-1.5 w-1.5 rounded-full bg-primary ring-1 ring-white" />
55
+ ) : null}
56
+ </span>
50
57
  <span className="max-w-full truncate">{item.label}</span>
51
58
  </Link>
52
59
  </li>
@@ -55,7 +55,7 @@ export function DocBrowserAddressToolbar({
55
55
  onChange={(e) => onUrlInputChange(e.target.value)}
56
56
  aria-label={t('docBrowserAddressLabel')}
57
57
  placeholder={placeholder}
58
- className="h-8 w-full rounded-lg border border-border/75 bg-background pl-8 pr-3 text-xs text-foreground transition-colors placeholder:text-muted-foreground/55 focus:border-border focus:outline-none focus:ring-0"
58
+ className="h-8 w-full rounded-lg border border-border/75 bg-background pl-8 pr-3 text-xs text-foreground transition-colors placeholder:text-muted-foreground/55 focus:outline-none focus:ring-0"
59
59
  />
60
60
  </form>
61
61
  <IconActionButton
@@ -63,7 +63,7 @@ export function ServerPathPickerDirectoryList({
63
63
  'grid w-full grid-cols-[minmax(0,1fr)_8rem] items-center rounded-sm border border-transparent px-2 py-1.5 text-left text-sm text-foreground outline-none transition-colors',
64
64
  selected
65
65
  ? 'border-primary/25 bg-primary/15'
66
- : 'hover:bg-accent/70 focus-visible:border-primary/35 focus-visible:bg-accent/70',
66
+ : 'hover:bg-[var(--interaction-hover)] focus-visible:border-primary/35 focus-visible:bg-accent/70',
67
67
  )}
68
68
  onClick={() => onSelect(entry.path)}
69
69
  onDoubleClick={() => onOpen(entry.path)}
@@ -112,7 +112,7 @@ export function ServerPathPickerLocations({
112
112
  type="button"
113
113
  className={cn(
114
114
  'flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
115
- active ? 'bg-primary/12 text-primary' : 'text-foreground hover:bg-accent',
115
+ active ? 'bg-primary/12 text-primary' : 'text-foreground hover:bg-[var(--interaction-hover)]',
116
116
  )}
117
117
  aria-current={active ? 'location' : undefined}
118
118
  onClick={() => onNavigate(item.path)}
@@ -99,7 +99,7 @@ export function ServerPathPickerNavigationBar({
99
99
  disabled={!canGoUp || disabled}
100
100
  />
101
101
 
102
- <div className="ml-1 flex h-9 min-w-0 flex-1 items-center overflow-hidden rounded-md border border-border bg-background shadow-sm focus-within:ring-1 focus-within:ring-primary/50">
102
+ <div className="ml-1 flex h-9 min-w-0 flex-1 items-center overflow-hidden rounded-md border border-border bg-background shadow-sm">
103
103
  {editingAddress ? (
104
104
  <Input
105
105
  value={addressDraft}
@@ -136,7 +136,7 @@ export function ServerPathPickerNavigationBar({
136
136
  <>
137
137
  <button
138
138
  type="button"
139
- className="rounded px-1.5 py-1 text-sm text-muted-foreground hover:bg-accent"
139
+ className="rounded px-1.5 py-1 text-sm text-muted-foreground hover:bg-[var(--interaction-hover)]"
140
140
  aria-label={t('pathPickerShowFullAddress')}
141
141
  onClick={beginAddressEdit}
142
142
  >
@@ -149,7 +149,7 @@ export function ServerPathPickerNavigationBar({
149
149
  ) : null}
150
150
  <button
151
151
  type="button"
152
- className="max-w-40 truncate rounded px-1.5 py-1 text-sm text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
152
+ className="max-w-40 truncate rounded px-1.5 py-1 text-sm text-foreground hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
153
153
  onClick={() => onNavigate(breadcrumb.path)}
154
154
  disabled={disabled}
155
155
  >
@@ -31,7 +31,7 @@ describe('IconActionButton', () => {
31
31
  const button = screen.getByRole('button', { name: 'More' });
32
32
  expect(button.className).toContain('h-7');
33
33
  expect(button.className).toContain('w-7');
34
- expect(button.className).toContain('hover:bg-accent');
34
+ expect(button.className).toContain('hover:bg-[var(--interaction-hover)]');
35
35
  });
36
36
 
37
37
  it('ignores restored pointer focus while preserving keyboard tooltip focus', () => {
@@ -31,7 +31,7 @@ const SIZE_CLASS: Record<IconActionButtonSize, string> = {
31
31
 
32
32
  const TONE_CLASS: Record<IconActionButtonTone, string> = {
33
33
  default:
34
- 'text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
34
+ 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
35
35
  strong:
36
36
  'text-muted-foreground hover:bg-black/10 hover:text-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
37
37
  };
@@ -9,12 +9,12 @@ const buttonVariants = cva(
9
9
  variant: {
10
10
  default: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
11
11
  destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
12
- outline: 'border border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground',
13
- secondary: 'bg-muted text-foreground hover:bg-accent hover:text-accent-foreground',
14
- ghost: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
12
+ outline: 'border border-border bg-card text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
13
+ secondary: 'bg-muted text-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
14
+ ghost: 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
15
15
  link: 'text-primary underline-offset-4 hover:underline',
16
16
  primary: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
17
- subtle: 'bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground',
17
+ subtle: 'bg-muted text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
18
18
  'primary-outline': 'border border-primary/30 text-primary hover:bg-primary hover:text-primary-foreground'
19
19
  },
20
20
  size: {
@@ -42,7 +42,7 @@ const DialogContent = React.forwardRef<
42
42
  {...props}
43
43
  >
44
44
  {children}
45
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 transition-all duration-fast hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:pointer-events-none">
45
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 transition-all duration-fast hover:bg-[var(--interaction-hover)] hover:text-accent-foreground hover:opacity-100 focus:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:pointer-events-none">
46
46
  <X className="h-4 w-4 text-muted-foreground" />
47
47
  <span className="sr-only">Close</span>
48
48
  </DialogPrimitive.Close>
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
9
  <input
10
10
  type={type}
11
11
  className={cn(
12
- 'flex h-9 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2 text-sm text-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0 focus:border-border disabled:cursor-not-allowed disabled:opacity-50',
12
+ 'flex h-9 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2 text-sm text-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50',
13
13
  className
14
14
  )}
15
15
  ref={ref}
@@ -5,6 +5,8 @@ interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
5
5
  children: React.ReactNode;
6
6
  }
7
7
 
8
+ export const SCROLL_BOTTOM_EDGE_FADE_CLASS = '[mask-image:linear-gradient(to_bottom,black_calc(100%_-_28px),transparent)] [-webkit-mask-image:linear-gradient(to_bottom,black_calc(100%_-_28px),transparent)]';
9
+
8
10
  const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
9
11
  ({ className, children, ...props }, ref) => (
10
12
  <div
@@ -119,7 +119,7 @@ const SelectItem = React.forwardRef<
119
119
  <SelectPrimitive.Item
120
120
  ref={ref}
121
121
  className={cn(
122
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-accent",
122
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-[var(--interaction-hover)]",
123
123
  className
124
124
  )}
125
125
  {...props}
@@ -18,7 +18,7 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
18
18
  ref={ref}
19
19
  className={cn(
20
20
  'switch-track peer inline-flex h-[22px] w-10 shrink-0 cursor-pointer items-center rounded-full border-2 transition-colors duration-fast focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:cursor-not-allowed disabled:opacity-50',
21
- checked ? 'border-primary/70 bg-primary' : 'border-border/60 bg-muted hover:bg-accent',
21
+ checked ? 'border-primary/70 bg-primary' : 'border-border/60 bg-muted hover:bg-[var(--interaction-hover)]',
22
22
  className
23
23
  )}
24
24
  onClick={() => onCheckedChange?.(!checked)}
@@ -67,7 +67,7 @@ export function TabsTrigger({ value, children, className }: TabsTriggerProps) {
67
67
  'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-[13px] font-medium transition-all duration-fast focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:pointer-events-none disabled:opacity-50',
68
68
  isActive
69
69
  ? 'bg-card text-foreground shadow-sm'
70
- : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
70
+ : 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
71
71
  className
72
72
  )}
73
73
  >
@@ -7,7 +7,7 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
7
7
  ({ className, ...props }, ref) => (
8
8
  <textarea
9
9
  className={cn(
10
- "flex min-h-28 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2.5 text-sm text-foreground placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0 focus:border-border disabled:cursor-not-allowed disabled:opacity-50",
10
+ "flex min-h-28 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2.5 text-sm text-foreground placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50",
11
11
  className,
12
12
  )}
13
13
  ref={ref}
@@ -39,6 +39,7 @@ export type SessionContextWindowView = {
39
39
  export type SessionActivityPreviewView = {
40
40
  state: "running" | "completed" | "failed" | "cancelled" | "idle";
41
41
  timestamp: string;
42
+ statusKind?: "thinking" | "tool-running" | "tool-completed" | "run-failed" | "run-interrupted";
42
43
  statusText?: string;
43
44
  replyText?: string;
44
45
  };
@@ -68,6 +68,7 @@ export type ProviderInstanceView = {
68
68
  isCustom: boolean;
69
69
  enabled: boolean;
70
70
  displayName?: string;
71
+ apiKeyRequired?: boolean;
71
72
  apiKeySet: boolean;
72
73
  apiKeyMasked?: string;
73
74
  apiBase?: string | null;
@@ -621,6 +622,7 @@ export type ProviderTemplateView = {
621
622
  envKey: string;
622
623
  isGateway?: boolean;
623
624
  isLocal?: boolean;
625
+ apiKeyRequired?: boolean;
624
626
  defaultApiBase?: string;
625
627
  logo?: string;
626
628
  apiBaseHelp?: {
@@ -7,6 +7,7 @@ import zhCron from './locales/zh-CN/cron.json';
7
7
  import zhDesktopUpdate from './locales/zh-CN/desktop-update.json';
8
8
  import zhDocBrowser from './locales/zh-CN/doc-browser.json';
9
9
  import zhMarketplace from './locales/zh-CN/marketplace.json';
10
+ import zhInbox from './locales/zh-CN/inbox.json';
10
11
  import zhPathPicker from './locales/zh-CN/path-picker.json';
11
12
  import zhPwa from './locales/zh-CN/pwa.json';
12
13
  import zhRemote from './locales/zh-CN/remote.json';
@@ -21,6 +22,7 @@ import enCron from './locales/en-US/cron.json';
21
22
  import enDesktopUpdate from './locales/en-US/desktop-update.json';
22
23
  import enDocBrowser from './locales/en-US/doc-browser.json';
23
24
  import enMarketplace from './locales/en-US/marketplace.json';
25
+ import enInbox from './locales/en-US/inbox.json';
24
26
  import enPathPicker from './locales/en-US/path-picker.json';
25
27
  import enPwa from './locales/en-US/pwa.json';
26
28
  import enRemote from './locales/en-US/remote.json';
@@ -54,6 +56,7 @@ const zhCatalog: MessageCatalog = {
54
56
  ...zhChat,
55
57
  ...zhAgents,
56
58
  ...zhMarketplace,
59
+ ...zhInbox,
57
60
  ...zhDocBrowser,
58
61
  ...zhPathPicker,
59
62
  ...zhPwa,
@@ -71,6 +74,7 @@ const enCatalog: MessageCatalog = {
71
74
  ...enChat,
72
75
  ...enAgents,
73
76
  ...enMarketplace,
77
+ ...enInbox,
74
78
  ...enDocBrowser,
75
79
  ...enPathPicker,
76
80
  ...enPwa,
@@ -202,6 +202,7 @@
202
202
  "chatRuntimeInitializationFailed": "Chat startup failed. Please retry in a moment or inspect the service logs.",
203
203
  "chatInputPlaceholder": "Type a message, / for commands, apps or skills, @ for files, folders, projects or panel apps, Enter to send, Shift + Enter for newline",
204
204
  "chatInputPlaceholderCompact": "Message NextClaw...",
205
+ "chatWelcomeInputPlaceholder": "Describe what you want to get done…",
205
206
  "chatInputHint": "Multi-turn context is preserved in the current session.",
206
207
  "chatSlashSection": "Slash menu",
207
208
  "chatSlashSectionCommands": "Commands",
@@ -283,6 +284,7 @@
283
284
  "chatModelSearchEmpty": "No matching models",
284
285
  "chatFavoriteModel": "Add to favorites",
285
286
  "chatUnfavoriteModel": "Remove from favorites",
287
+ "chatManageModels": "Manage models and providers",
286
288
  "chatPickerAllSkills": "All skills",
287
289
  "chatSend": "Send",
288
290
  "chatStop": "Stop",
@@ -353,6 +355,7 @@
353
355
  "chatProjectExistingAdded": "Project added",
354
356
  "chatProjectExistingFailed": "Failed to add project",
355
357
  "chatSessionErrorTitle": "Something went wrong",
358
+ "chatSessionActivityInterrupted": "The run was interrupted. Send the message again.",
356
359
  "chatSendFailed": "Failed to send message",
357
360
  "chatErrorDetails": "Details",
358
361
  "chatRoleUser": "You",
@@ -419,24 +422,23 @@
419
422
  "chatSidebarExpandProject": "Expand project",
420
423
  "chatSidebarCollapseProject": "Collapse project",
421
424
  "chatSidebarProjectViewEmpty": "No projects yet",
422
- "chatWelcomeTitle": "Hello, how can I help you?",
423
- "chatWelcomeSubtitle": "Describe the goal, choose the project context, and let NextClaw take it from there.",
425
+ "chatBackgroundReplyFallbackTitle": "New reply",
426
+ "chatBackgroundReplyFallbackPreview": "A reply is ready. Open the session to view it.",
427
+ "chatBackgroundReplyOpenAriaLabel": "Open the new reply in {title}",
428
+ "chatWelcomeTitle": "What would you like to get done?",
429
+ "chatWelcomeContextLabel": "Task context",
424
430
  "chatWelcomeProjectPickerLabel": "Choose working directory",
425
431
  "chatWelcomeProjectPickerPlaceholder": "Choose working directory",
426
- "chatWelcomeProjectDefaultBadge": "Default",
427
432
  "chatWelcomeProjectRecentTitle": "Recent projects",
428
433
  "chatWelcomeProjectNoRecent": "No recent projects yet",
429
434
  "chatWelcomeProjectOpenFolder": "Open folder",
430
435
  "chatWelcomeSessionTypePickerLabel": "Choose session type",
431
- "chatWelcomeCapability1Title": "Smart Conversations",
432
- "chatWelcomeCapability1Desc": "Multi-turn context conversations with multiple AI models",
433
- "chatWelcomeCapability1Prompt": "Help me understand this project and propose the next three concrete things we should build.",
434
- "chatWelcomeCapability2Title": "Skill Extensions",
435
- "chatWelcomeCapability2Desc": "Extend Agent capabilities by installing skills",
436
- "chatWelcomeCapability2Prompt": "Review the skills that would be useful for this project and recommend which ones I should enable first.",
437
- "chatWelcomeCapability3Title": "Scheduled Tasks",
438
- "chatWelcomeCapability3Desc": "Set up scheduled automated tasks",
439
- "chatWelcomeCapability3Prompt": "Help me design a scheduled automation for this project, including the trigger, message, and safety checks.",
436
+ "chatWelcomeSuggestion1Label": "Review this project",
437
+ "chatWelcomeSuggestion1Prompt": "Help me understand this project and propose the next three concrete things we should build.",
438
+ "chatWelcomeSuggestion2Label": "Find useful skills",
439
+ "chatWelcomeSuggestion2Prompt": "Review the skills that would be useful for this project and recommend which ones I should enable first.",
440
+ "chatWelcomeSuggestion3Label": "Create a scheduled task",
441
+ "chatWelcomeSuggestion3Prompt": "Help me design a scheduled automation for this project, including the trigger, message, and safety checks.",
440
442
  "chatSkillsPickerTitle": "Skills",
441
443
  "chatSkillsPickerEmpty": "No skills installed",
442
444
  "chatSkillsPickerSearchPlaceholder": "Search skills",
@@ -444,9 +446,9 @@
444
446
  "chatSkillsPickerOfficial": "Official",
445
447
  "chatSkillsPickerManage": "Manage Skills",
446
448
  "chatSkillPreviewUnavailable": "This skill file is unavailable. It may have been moved or removed.",
449
+ "chatInputAdd": "Add content",
447
450
  "chatInputAttach": "Attach file",
448
451
  "chatInputAttachmentUnsupported": "This file type is not supported in the current upload flow.",
449
452
  "chatInputAttachmentTooLarge": "Files must be {maxMb} MB or smaller.",
450
- "chatInputAttachmentReadFailed": "Failed to read the file. Please try again.",
451
- "chatInputAttachComingSoon": "Coming soon"
453
+ "chatInputAttachmentReadFailed": "Failed to read the file. Please try again."
452
454
  }
@@ -0,0 +1,30 @@
1
+ {
2
+ "inboxTitle": "Inbox",
3
+ "inboxDescription": "Reports, recommendations, and articles delivered by AI",
4
+ "inboxUnread": "Unread",
5
+ "inboxAll": "All",
6
+ "inboxArchived": "Archived",
7
+ "inboxEmptyTitle": "Nothing here yet",
8
+ "inboxEmptyDescription": "Reports, recommendations, and articles from AI will be saved here.",
9
+ "inboxSelectTitle": "Select something to read",
10
+ "inboxSelectDescription": "Open an item from the list and its reading state will stay in sync.",
11
+ "inboxDeliveredBy": "Delivered by AI",
12
+ "inboxReaderPosition": "{current} of {total}",
13
+ "inboxPrevious": "Previous",
14
+ "inboxNext": "Next",
15
+ "inboxOpenInbox": "Open in Inbox",
16
+ "inboxReadLater": "Read later",
17
+ "inboxMarkRead": "Mark as read",
18
+ "inboxMarkUnread": "Mark as unread",
19
+ "inboxArchive": "Archive",
20
+ "inboxRestore": "Restore",
21
+ "inboxDelete": "Delete",
22
+ "inboxContinueChat": "Continue in chat",
23
+ "inboxDeleteTitle": "Delete this item?",
24
+ "inboxDeleteDescription": "It cannot be recovered from Inbox. Any chat already created will remain.",
25
+ "inboxDeleteConfirm": "Delete",
26
+ "inboxLoadError": "Inbox could not be loaded. Try again shortly.",
27
+ "inboxActionError": "The action failed. Please try again.",
28
+ "inboxUnreadCount": "{count} unread",
29
+ "inboxNoSummary": "No summary"
30
+ }
@@ -202,6 +202,7 @@
202
202
  "chatRuntimeInitializationFailed": "聊天能力启动失败,请稍后重试或检查服务日志。",
203
203
  "chatInputPlaceholder": "输入消息,/ 选命令、应用或技能,@ 引用文件、目录、项目或面板应用,Enter 发送,Shift + Enter 换行",
204
204
  "chatInputPlaceholderCompact": "发消息...",
205
+ "chatWelcomeInputPlaceholder": "描述你想完成的目标……",
205
206
  "chatInputHint": "支持多轮上下文,默认走当前会话。",
206
207
  "chatSlashSection": "斜杠菜单",
207
208
  "chatSlashSectionCommands": "命令",
@@ -283,6 +284,7 @@
283
284
  "chatModelSearchEmpty": "没有匹配的模型",
284
285
  "chatFavoriteModel": "收藏模型",
285
286
  "chatUnfavoriteModel": "取消收藏",
287
+ "chatManageModels": "管理模型与提供商",
286
288
  "chatPickerAllSkills": "全部技能",
287
289
  "chatSend": "发送",
288
290
  "chatStop": "停止",
@@ -353,6 +355,7 @@
353
355
  "chatProjectExistingAdded": "项目已添加",
354
356
  "chatProjectExistingFailed": "添加项目失败",
355
357
  "chatSessionErrorTitle": "出错了",
358
+ "chatSessionActivityInterrupted": "运行意外中断,请重新发送消息。",
356
359
  "chatSendFailed": "发送消息失败",
357
360
  "chatErrorDetails": "查看详情",
358
361
  "chatRoleUser": "你",
@@ -419,24 +422,23 @@
419
422
  "chatSidebarExpandProject": "展开项目",
420
423
  "chatSidebarCollapseProject": "收起项目",
421
424
  "chatSidebarProjectViewEmpty": "还没有项目",
422
- "chatWelcomeTitle": "你好,有什么可以帮你的吗?",
423
- "chatWelcomeSubtitle": "描述目标,选择项目上下文,让 NextClaw 接住这次任务。",
425
+ "chatBackgroundReplyFallbackTitle": "有新回复",
426
+ "chatBackgroundReplyFallbackPreview": "回复已完成,点击查看。",
427
+ "chatBackgroundReplyOpenAriaLabel": "打开“{title}”的新回复",
428
+ "chatWelcomeTitle": "今天想完成什么?",
429
+ "chatWelcomeContextLabel": "任务上下文",
424
430
  "chatWelcomeProjectPickerLabel": "选择工作目录",
425
431
  "chatWelcomeProjectPickerPlaceholder": "选择工作目录",
426
- "chatWelcomeProjectDefaultBadge": "默认",
427
432
  "chatWelcomeProjectRecentTitle": "最近项目",
428
433
  "chatWelcomeProjectNoRecent": "暂无最近项目",
429
434
  "chatWelcomeProjectOpenFolder": "打开文件夹",
430
435
  "chatWelcomeSessionTypePickerLabel": "选择会话类型",
431
- "chatWelcomeCapability1Title": "智能对话",
432
- "chatWelcomeCapability1Desc": "多轮上下文对话,支持多种 AI 模型",
433
- "chatWelcomeCapability1Prompt": "帮我理解当前项目,并给出接下来最值得推进的三个具体建设方向。",
434
- "chatWelcomeCapability2Title": "技能扩展",
435
- "chatWelcomeCapability2Desc": "通过安装技能扩展 Agent 能力",
436
- "chatWelcomeCapability2Prompt": "帮我看看这个项目适合启用哪些技能,并推荐我优先配置哪些能力。",
437
- "chatWelcomeCapability3Title": "定时任务",
438
- "chatWelcomeCapability3Desc": "设置定时执行的自动化任务",
439
- "chatWelcomeCapability3Prompt": "帮我设计一个适合当前项目的定时自动化任务,包括触发条件、消息内容和安全检查。",
436
+ "chatWelcomeSuggestion1Label": "梳理当前项目",
437
+ "chatWelcomeSuggestion1Prompt": "帮我理解当前项目,并给出接下来最值得推进的三个具体建设方向。",
438
+ "chatWelcomeSuggestion2Label": "推荐实用技能",
439
+ "chatWelcomeSuggestion2Prompt": "帮我看看这个项目适合启用哪些技能,并推荐我优先配置哪些能力。",
440
+ "chatWelcomeSuggestion3Label": "创建定时任务",
441
+ "chatWelcomeSuggestion3Prompt": "帮我设计一个适合当前项目的定时自动化任务,包括触发条件、消息内容和安全检查。",
440
442
  "chatSkillsPickerTitle": "技能",
441
443
  "chatSkillsPickerEmpty": "暂无已安装技能",
442
444
  "chatSkillsPickerSearchPlaceholder": "搜索技能",
@@ -444,9 +446,9 @@
444
446
  "chatSkillsPickerOfficial": "官方",
445
447
  "chatSkillsPickerManage": "管理技能",
446
448
  "chatSkillPreviewUnavailable": "无法打开这个技能文件,它可能已被移动或删除。",
449
+ "chatInputAdd": "添加内容",
447
450
  "chatInputAttach": "添加附件",
448
451
  "chatInputAttachmentUnsupported": "当前上传流程不支持该文件类型。",
449
452
  "chatInputAttachmentTooLarge": "文件不能超过 {maxMb} MB。",
450
- "chatInputAttachmentReadFailed": "读取文件失败,请重试。",
451
- "chatInputAttachComingSoon": "即将支持"
453
+ "chatInputAttachmentReadFailed": "读取文件失败,请重试。"
452
454
  }
@@ -0,0 +1,30 @@
1
+ {
2
+ "inboxTitle": "收件箱",
3
+ "inboxDescription": "AI 主动送达的报告、推荐和文章",
4
+ "inboxUnread": "未读",
5
+ "inboxAll": "全部",
6
+ "inboxArchived": "已归档",
7
+ "inboxEmptyTitle": "这里还没有内容",
8
+ "inboxEmptyDescription": "AI 送达的报告、推荐和文章会保存在这里。",
9
+ "inboxSelectTitle": "选择一项开始阅读",
10
+ "inboxSelectDescription": "打开左侧列表中的内容,阅读状态会在这里同步。",
11
+ "inboxDeliveredBy": "由 AI 送达",
12
+ "inboxReaderPosition": "第 {current} 项,共 {total} 项",
13
+ "inboxPrevious": "上一项",
14
+ "inboxNext": "下一项",
15
+ "inboxOpenInbox": "在收件箱中打开",
16
+ "inboxReadLater": "稍后阅读",
17
+ "inboxMarkRead": "标记已读",
18
+ "inboxMarkUnread": "标记未读",
19
+ "inboxArchive": "归档",
20
+ "inboxRestore": "恢复",
21
+ "inboxDelete": "删除",
22
+ "inboxContinueChat": "继续聊",
23
+ "inboxDeleteTitle": "删除这项内容?",
24
+ "inboxDeleteDescription": "删除后将无法从收件箱恢复,但已经创建的会话不会被删除。",
25
+ "inboxDeleteConfirm": "删除",
26
+ "inboxLoadError": "无法加载收件箱,请稍后重试。",
27
+ "inboxActionError": "操作失败,请重试。",
28
+ "inboxUnreadCount": "{count} 项未读",
29
+ "inboxNoSummary": "没有摘要"
30
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildProviderModelCatalog } from '@/shared/lib/provider-models';
3
+
4
+ describe('anonymous provider configuration', () => {
5
+ it('includes an enabled provider that does not require a user API key', () => {
6
+ const catalog = buildProviderModelCatalog({
7
+ onlyConfigured: true,
8
+ providersView: {
9
+ providers: {
10
+ opencode: {
11
+ providerId: 'opencode',
12
+ providerType: 'opencode',
13
+ isBuiltInType: true,
14
+ isCustom: false,
15
+ enabled: true,
16
+ apiKeyRequired: false,
17
+ apiKeySet: false,
18
+ models: ['opencode/big-pickle']
19
+ }
20
+ }
21
+ },
22
+ templatesView: {
23
+ providerTemplates: [
24
+ {
25
+ id: 'opencode',
26
+ providerType: 'opencode',
27
+ displayName: 'OpenCode Zen Free Trial',
28
+ modelPrefix: 'opencode',
29
+ defaultModels: ['opencode/big-pickle'],
30
+ keywords: ['opencode'],
31
+ envKey: 'OPENCODE_API_KEY'
32
+ }
33
+ ]
34
+ }
35
+ });
36
+
37
+ expect(catalog).toHaveLength(1);
38
+ expect(catalog[0]?.models).toEqual(['big-pickle']);
39
+ });
40
+ });
@@ -201,12 +201,13 @@ export function findProviderByModel(
201
201
  return null;
202
202
  }
203
203
 
204
- function isProviderConfigured(provider: ProviderConfigView | undefined): boolean {
204
+ export function isProviderConfigured(provider: ProviderConfigView | undefined): boolean {
205
205
  if (!provider) {
206
206
  return false;
207
207
  }
208
- // Keep in sync with ProvidersList "已配置" tab: only enabled providers with apiKey count as configured.
209
- return provider.enabled !== false && provider.apiKeySet === true;
208
+ return provider.enabled !== false && (
209
+ provider.apiKeyRequired === false || provider.apiKeySet === true
210
+ );
210
211
  }
211
212
 
212
213
  export function buildProviderModelCatalog(params: {