@trismegisto/spine 3.3.5 → 3.3.6

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 (70) hide show
  1. package/README.md +4 -2
  2. package/fesm2022/trismegisto-spine-audit.mjs +20 -2
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-D8HUpZtA.mjs → trismegisto-spine-auto-service-auto-service-audit.component-CszXNvSs.mjs} +2 -2
  5. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-D8HUpZtA.mjs.map → trismegisto-spine-auto-service-auto-service-audit.component-CszXNvSs.mjs.map} +1 -1
  6. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client-picker.component-NawbfdLW.mjs → trismegisto-spine-auto-service-auto-service-client-picker.component-CknHYd_P.mjs} +7 -3
  7. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-CknHYd_P.mjs.map +1 -0
  8. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-kAXaCM5W.mjs → trismegisto-spine-auto-service-auto-service-client.component-Dg3mJrpz.mjs} +24 -5
  9. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-Dg3mJrpz.mjs.map +1 -0
  10. package/fesm2022/{trismegisto-spine-auto-service-auto-service-delete-CV_t9vxm.mjs → trismegisto-spine-auto-service-auto-service-delete-BWY5G-xe.mjs} +2 -2
  11. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-BWY5G-xe.mjs.map +1 -0
  12. package/fesm2022/{trismegisto-spine-auto-service-auto-service-groups.component-DE2QIGRH.mjs → trismegisto-spine-auto-service-auto-service-groups.component-DQqzfq0M.mjs} +21 -5
  13. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-DQqzfq0M.mjs.map +1 -0
  14. package/fesm2022/{trismegisto-spine-auto-service-auto-service-login-attempts.component-DoyhkSYc.mjs → trismegisto-spine-auto-service-auto-service-login-attempts.component-CjxKYv-y.mjs} +2 -2
  15. package/fesm2022/{trismegisto-spine-auto-service-auto-service-login-attempts.component-DoyhkSYc.mjs.map → trismegisto-spine-auto-service-auto-service-login-attempts.component-CjxKYv-y.mjs.map} +1 -1
  16. package/fesm2022/{trismegisto-spine-auto-service-auto-service-news-page.component-BW7BJ0oD.mjs → trismegisto-spine-auto-service-auto-service-news-page.component-Cbaszx4F.mjs} +21 -5
  17. package/fesm2022/trismegisto-spine-auto-service-auto-service-news-page.component-Cbaszx4F.mjs.map +1 -0
  18. package/fesm2022/{trismegisto-spine-auto-service-auto-service-ratings-page.component-CHPU-QLJ.mjs → trismegisto-spine-auto-service-auto-service-ratings-page.component-CJRIDWXi.mjs} +22 -4
  19. package/fesm2022/trismegisto-spine-auto-service-auto-service-ratings-page.component-CJRIDWXi.mjs.map +1 -0
  20. package/fesm2022/{trismegisto-spine-auto-service-auto-service-suggestions-page.component-CkiCTmXD.mjs → trismegisto-spine-auto-service-auto-service-suggestions-page.component-Bu8YX554.mjs} +46 -4
  21. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-Bu8YX554.mjs.map +1 -0
  22. package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-D58Ht9PH.mjs → trismegisto-spine-auto-service-auto-service.component-BLkLHp6J.mjs} +23 -5
  23. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLkLHp6J.mjs.map +1 -0
  24. package/fesm2022/{trismegisto-spine-auto-service-home.component-9WvXOH3l.mjs → trismegisto-spine-auto-service-home.component-BYMiTZp6.mjs} +6 -3
  25. package/fesm2022/trismegisto-spine-auto-service-home.component-BYMiTZp6.mjs.map +1 -0
  26. package/fesm2022/trismegisto-spine-auto-service.mjs +10 -10
  27. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  28. package/fesm2022/trismegisto-spine-layout-ai-agent-chat.component-C5JljPYD.mjs +294 -0
  29. package/fesm2022/trismegisto-spine-layout-ai-agent-chat.component-C5JljPYD.mjs.map +1 -0
  30. package/fesm2022/{trismegisto-spine-layout-suggestions-page.component-CEKMqera.mjs → trismegisto-spine-layout-suggestions-page.component-Bh3VGlOR.mjs} +18 -4
  31. package/fesm2022/trismegisto-spine-layout-suggestions-page.component-Bh3VGlOR.mjs.map +1 -0
  32. package/fesm2022/{trismegisto-spine-layout-trismegisto-spine-layout-DiAfxIGP.mjs → trismegisto-spine-layout-trismegisto-spine-layout-B47sMo_4.mjs} +1012 -57
  33. package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-B47sMo_4.mjs.map +1 -0
  34. package/fesm2022/trismegisto-spine-layout.mjs +1 -1
  35. package/fesm2022/trismegisto-spine-modal.mjs +4 -1
  36. package/fesm2022/trismegisto-spine-modal.mjs.map +1 -1
  37. package/fesm2022/trismegisto-spine-notification.mjs +1 -1
  38. package/fesm2022/trismegisto-spine-notification.mjs.map +1 -1
  39. package/fesm2022/trismegisto-spine-routing.mjs +2 -2
  40. package/fesm2022/trismegisto-spine-routing.mjs.map +1 -1
  41. package/fesm2022/trismegisto-spine-shell.mjs +2 -1
  42. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  43. package/fesm2022/trismegisto-spine-side-panel.mjs +4 -1
  44. package/fesm2022/trismegisto-spine-side-panel.mjs.map +1 -1
  45. package/fesm2022/trismegisto-spine.mjs +200 -69
  46. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  47. package/package.json +6 -6
  48. package/types/trismegisto-spine-audit.d.ts +11 -4
  49. package/types/trismegisto-spine-audit.d.ts.map +1 -1
  50. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  51. package/types/trismegisto-spine-layout.d.ts +124 -105
  52. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  53. package/types/trismegisto-spine-modal.d.ts +2 -1
  54. package/types/trismegisto-spine-modal.d.ts.map +1 -1
  55. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  56. package/types/trismegisto-spine-side-panel.d.ts +2 -1
  57. package/types/trismegisto-spine-side-panel.d.ts.map +1 -1
  58. package/types/trismegisto-spine.d.ts +104 -28
  59. package/types/trismegisto-spine.d.ts.map +1 -1
  60. package/fesm2022/trismegisto-spine-auto-service-auto-service-client-picker.component-NawbfdLW.mjs.map +0 -1
  61. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-kAXaCM5W.mjs.map +0 -1
  62. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-CV_t9vxm.mjs.map +0 -1
  63. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-DE2QIGRH.mjs.map +0 -1
  64. package/fesm2022/trismegisto-spine-auto-service-auto-service-news-page.component-BW7BJ0oD.mjs.map +0 -1
  65. package/fesm2022/trismegisto-spine-auto-service-auto-service-ratings-page.component-CHPU-QLJ.mjs.map +0 -1
  66. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-CkiCTmXD.mjs.map +0 -1
  67. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-D58Ht9PH.mjs.map +0 -1
  68. package/fesm2022/trismegisto-spine-auto-service-home.component-9WvXOH3l.mjs.map +0 -1
  69. package/fesm2022/trismegisto-spine-layout-suggestions-page.component-CEKMqera.mjs.map +0 -1
  70. package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-DiAfxIGP.mjs.map +0 -1
@@ -1,28 +1,84 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ChangeDetectorRef, ChangeDetectionStrategy, Component, InjectionToken, input, signal, computed, Injectable, PLATFORM_ID, effect, untracked, DestroyRef, Injector, model, ElementRef, output, Optional, Inject, viewChild, NgZone, isDevMode, Directive, makeEnvironmentProviders, provideEnvironmentInitializer, TemplateRef, ViewContainerRef, afterNextRender, ViewEncapsulation } from '@angular/core';
2
+ import { inject, ChangeDetectorRef, ChangeDetectionStrategy, Component, InjectionToken, input, signal, computed, Injectable, PLATFORM_ID, effect, untracked, DestroyRef, Injector, model, ElementRef, output, Optional, Inject, viewChild, NgZone, isDevMode, Directive, makeEnvironmentProviders, provideEnvironmentInitializer, TemplateRef, ViewContainerRef, afterNextRender, ViewEncapsulation, DOCUMENT } from '@angular/core';
3
3
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
4
4
  import * as i3 from '@angular/router';
5
5
  import { RouterLink, Router, NavigationEnd, NavigationSkipped, RouterOutlet } from '@angular/router';
6
6
  import { firstValueFrom, filter, Subject, take, Subscription, debounceTime, distinctUntilChanged } from 'rxjs';
7
- import { MenuButtonComponent, HermesThemeService, HERMES_THEME_CONFIG, HermesSidebarService, HermesToastService, IconComponent, ButtonComponent, SkeletonComponent, TextareaComponent, FloatingWindowComponent, IconButtonComponent, AccountAvatarComponent, BadgeComponent, PopoverComponent, PopoverTriggerDirective, HermesShortcutService, HeaderComponent as HeaderComponent$1, CommandPaletteComponent, panelIdsInOrder, createPersistedWorkspaceStore, HERMES_WORKSPACE_PERSISTENCE_CONFIG, findPanelTabs, canSplitPanel, cyclicPanelNeighbor, ContextMenuDirective, SidebarComponent as SidebarComponent$1, SidebarNodeComponent, HermesI18nService, SegmentedControlComponent, ThemePickerComponent, AlertComponent, BreadcrumbComponent, DrawerComponent, ModalComponent, PaginatorComponent, RichTextComponent, SearchFieldComponent, ShortcutListComponent, TagComponent, CodeInputComponent, InputComponent, RadioComponent, CheckboxComponent, CodeViewerComponent, SelectComponent, AvatarGroupComponent, HERMES_SIDEBAR_CONFIG, ComboboxComponent, SettingsDialogComponent, SettingsRowComponent, SettingsSectionComponent, ToggletipComponent, TooltipDirective, LoadingSquareComponent, registerUnsavedChanges, HermesUnsavedChangesService, WorkspaceComponent, ToastStackComponent, SkipLinkComponent, MessageScrollerComponent, BubbleComponent, MessageComponent, MessageDividerComponent } from '@trismegisto/angular';
8
- import { HermesSidebarService as HermesSidebarService$1 } from '@trismegisto/angular/sidebar';
7
+ import { SkipLinkComponent } from '@trismegisto/angular/skip-link';
8
+ import { HermesToastService, ToastStackComponent } from '@trismegisto/angular/toast';
9
+ import { WorkspaceComponent } from '@trismegisto/angular/workspace';
10
+ import { HermesSidebarService, SidebarComponent as SidebarComponent$1, SidebarNodeComponent, HERMES_SIDEBAR_CONFIG } from '@trismegisto/angular/sidebar';
9
11
  import { ModalComponent as ModalComponent$1 } from '@trismegisto/spine/modal';
10
12
  import { SidePanelComponent } from '@trismegisto/spine/side-panel';
11
13
  import * as i2$1 from '@trismegisto/spine';
12
- import { SPINE_MESSAGES, SPINE_CONFIG, ThemeService, resolveSpineLogo, SpinePreferencesService, SpineQuickAccessService, DensityService, ThemePresetService, SPINE_ADMIN_API, SpineUserStorage, SPINE_USER_STORAGE_KEYS, spineRoutesOf, routerIdOf, SPINE_IDLE_OPTIONS, APP_PACKAGE_INFO, registerSpineBuiltinPage } from '@trismegisto/spine';
14
+ import { SPINE_MESSAGES, SPINE_CONFIG, ThemeService, resolveSpineLogo, SpinePreferencesService, SpineQuickAccessService, DensityService, ThemePresetService, SPINE_ADMIN_API, resolveSpineImage, SPINE_DEFAULT_AGENT_AVATAR, SPINE_DEFAULT_AGENT_IMAGE, SpineUserStorage, SPINE_USER_STORAGE_KEYS, spineRoutesOf, routerIdOf, SPINE_IDLE_OPTIONS, APP_PACKAGE_INFO, registerSpineBuiltinPage } from '@trismegisto/spine';
13
15
  import * as i2 from '@angular/common';
14
16
  import { CommonModule, NgTemplateOutlet, NgComponentOutlet, isPlatformBrowser, Location } from '@angular/common';
17
+ import { AccountAvatarComponent } from '@trismegisto/angular/account-avatar';
18
+ import { CommandPaletteComponent } from '@trismegisto/angular/command-palette';
19
+ import { HeaderComponent as HeaderComponent$1 } from '@trismegisto/angular/header';
20
+ import { IconComponent } from '@trismegisto/angular/icon';
21
+ import { IconButtonComponent } from '@trismegisto/angular/icon-button';
22
+ import { MenuButtonComponent } from '@trismegisto/angular/menu-button';
15
23
  import * as i1 from '@orfeu/angular/core';
16
24
  import { AuthorizationService, AUTH_CONFIG, ViewService, RealmAppsService } from '@orfeu/angular/core';
17
25
  import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
18
- import { urlPath, formatMessage, pluralMessage, detectShortcutPlatform, tabUrl, HERMES_FONTS, HERMES_TEXT_SCALES, formatShortcut, formatMessageHtml, weekdayNames, FALLBACK_FAMILY, findPanelTabs as findPanelTabs$1, isMessageContinuation } from '@trismegisto/primitives';
26
+ import { HermesThemeService, HERMES_THEME_CONFIG } from '@trismegisto/angular/theme';
27
+ import { formatMessage, pluralMessage } from '@trismegisto/primitives/i18n';
28
+ import { urlPath, formatMessageHtml } from '@trismegisto/primitives/html';
29
+ import { FloatingWindowComponent } from '@trismegisto/angular/floating-window';
30
+ import { ButtonComponent } from '@trismegisto/angular/button';
31
+ import { SkeletonComponent } from '@trismegisto/angular/skeleton';
32
+ import { TextareaComponent } from '@trismegisto/angular/textarea';
33
+ import { BadgeComponent } from '@trismegisto/angular/badge';
34
+ import { PopoverComponent, PopoverTriggerDirective } from '@trismegisto/angular/popover';
19
35
  import { NotificationService, AlertType } from '@trismegisto/spine/notification';
36
+ import { HermesShortcutService, ShortcutListComponent } from '@trismegisto/angular/shortcut-list';
37
+ import { detectShortcutPlatform, formatShortcut } from '@trismegisto/primitives/shortcuts';
38
+ import { ContextMenuDirective } from '@trismegisto/angular/menu';
39
+ import { createPersistedWorkspaceStore, HERMES_WORKSPACE_PERSISTENCE_CONFIG } from '@trismegisto/angular/workspace-persistence';
40
+ import { panelIdsInOrder, findPanelTabs, canSplitPanel, cyclicPanelNeighbor } from '@trismegisto/primitives/workspace';
41
+ import { tabUrl } from '@trismegisto/primitives/tab-bar';
20
42
  import { resolveEffectiveRouteChain, ComponentDistributorService, componentRouterId, LoggerService } from '@trismegisto/spine/routing';
43
+ import { HermesI18nService } from '@trismegisto/angular/i18n';
44
+ import { SegmentedControlComponent } from '@trismegisto/angular/segmented-control';
45
+ import { ThemePickerComponent } from '@trismegisto/angular/theme-picker';
46
+ import { HERMES_FONTS, FALLBACK_FAMILY } from '@trismegisto/primitives/theme';
47
+ import { HERMES_TEXT_SCALES } from '@trismegisto/primitives/zoom';
48
+ import { AlertComponent } from '@trismegisto/angular/alert';
49
+ import { BreadcrumbComponent } from '@trismegisto/angular/breadcrumb';
50
+ import { DrawerComponent } from '@trismegisto/angular/drawer';
51
+ import { ModalComponent } from '@trismegisto/angular/modal';
52
+ import { PaginatorComponent } from '@trismegisto/angular/paginator';
53
+ import { RichTextComponent } from '@trismegisto/angular/rich-text';
54
+ import { SearchFieldComponent } from '@trismegisto/angular/search-field';
55
+ import { CheckboxComponent } from '@trismegisto/angular/checkbox';
56
+ import { ComboboxComponent } from '@trismegisto/angular/combobox';
57
+ import { SelectComponent } from '@trismegisto/angular/select';
58
+ import { SettingsDialogComponent, SettingsRowComponent, SettingsSectionComponent } from '@trismegisto/angular/settings';
59
+ import { ToggletipComponent } from '@trismegisto/angular/toggletip';
60
+ import { weekdayNames } from '@trismegisto/primitives/date';
61
+ import { TagComponent } from '@trismegisto/angular/tag';
21
62
  import * as i1$1 from '@angular/forms';
22
63
  import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
64
+ import { CodeInputComponent } from '@trismegisto/angular/code-input';
65
+ import { InputComponent } from '@trismegisto/angular/input';
66
+ import { RadioComponent } from '@trismegisto/angular/radio';
67
+ import { CodeViewerComponent } from '@trismegisto/angular/code-viewer';
68
+ import { AvatarGroupComponent } from '@trismegisto/angular/avatar-group';
23
69
  import { Title, DomSanitizer } from '@angular/platform-browser';
70
+ import { LoadingSquareComponent } from '@trismegisto/angular/loading-square';
71
+ import { TooltipDirective } from '@trismegisto/angular/tooltip';
24
72
  import { Marked } from 'marked';
25
73
  import DOMPurify from 'dompurify';
74
+ import { FabComponent } from '@trismegisto/angular/fab';
75
+ import { PromptSuggestionsComponent } from '@trismegisto/angular/prompt-suggestions';
76
+ import { registerUnsavedChanges, HermesUnsavedChangesService } from '@trismegisto/angular/unsaved-changes';
77
+ import { BubbleComponent } from '@trismegisto/angular/bubble';
78
+ import { MessageComponent } from '@trismegisto/angular/message';
79
+ import { MessageDividerComponent } from '@trismegisto/angular/message-divider';
80
+ import { MessageScrollerComponent } from '@trismegisto/angular/message-scroller';
81
+ import { isMessageContinuation } from '@trismegisto/primitives/conversation';
26
82
 
27
83
  /**
28
84
  * Seletor de aplicações do realm: um `hm-menu-button` próprio, só com o ícone de grade, que abre a lista das
@@ -1856,6 +1912,511 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1856
1912
  `, styles: [":host{display:contents}.spine-inbox{position:relative;display:inline-flex}.spine-inbox__badge{position:absolute;inset-block-start:0;inset-inline-end:0;pointer-events:none;transform:translate(25%,-15%)}.spine-inbox__list{display:flex;flex-direction:column;inline-size:min(21rem,calc(100vw - 4rem));max-block-size:min(28rem,70dvh);margin:0;padding:0;overflow-x:hidden;overflow-y:auto;list-style:none}.spine-inbox__item{display:flex;gap:var(--hm-space-sm);inline-size:100%;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:0;border-radius:var(--hm-radius);background:none;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer}.spine-inbox__item:hover{background:var(--hm-hover)}.spine-inbox__item:focus-visible{outline:var(--hm-border-thin) solid var(--hm-focus-color);outline-offset:calc(var(--hm-border-thin) * -1)}.spine-inbox__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:2rem;block-size:2rem;border-radius:50%;background:var(--hm-accent-subtle);color:var(--hm-accent)}.spine-inbox__text{display:flex;flex:1;flex-direction:column;gap:var(--hm-space-micro);min-inline-size:0}.spine-inbox__source{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.spine-inbox__title{overflow:hidden;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);text-overflow:ellipsis;white-space:nowrap}.spine-inbox__body{display:-webkit-box;overflow:hidden;color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2}.spine-inbox__meta{display:flex;gap:var(--hm-space-micro);color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.spine-inbox__empty{margin:0;padding-block:var(--hm-space-sm);color:var(--hm-muted);font-size:var(--hm-font-size-sm)}\n"] }]
1857
1913
  }], ctorParameters: () => [] });
1858
1914
 
1915
+ const TOKEN_HEADER = 'x-access-token';
1916
+ const WS_PROTOCOL = 'graphql-transport-ws';
1917
+ const SUBSCRIPTION_ID = '1';
1918
+ const EVENTS_QUERY = `subscription ($agentId: ID!, $conversationId: ID!) {
1919
+ agentEvents(agentId: $agentId, conversationId: $conversationId) { type text tool title ok detail message }
1920
+ }`;
1921
+ const SEND_MUTATION = `mutation ($agentId: ID!, $input: SendAgentMessageInput!) {
1922
+ sendAgentMessage(agentId: $agentId, input: $input) { status message }
1923
+ }`;
1924
+ const RATE_MUTATION = `mutation ($agentId: ID!, $input: RateAgentMessageInput!) {
1925
+ rateAgentMessage(agentId: $agentId, input: $input) { status message }
1926
+ }`;
1927
+ const CONVERSATION_QUERY = `query ($agentId: ID!, $conversationId: ID!) {
1928
+ agentConversation(agentId: $agentId, conversationId: $conversationId) { status data { role text steps { tool title ok detail } } }
1929
+ }`;
1930
+ const AGENTS_QUERY = `query { agents { status data { id suggestions } } }`;
1931
+ /**
1932
+ * Assina os eventos de uma conversa e devolve a função que encerra a assinatura. Usa o WebSocket do navegador, com o
1933
+ * protocolo do graphql-ws: o shell também atende apps sem GraphQL, que não têm o pacote `graphql-ws` instalado.
1934
+ * `onClosed` avisa que a conexão caiu sem que quem assinou tenha pedido.
1935
+ */
1936
+ function subscribeAgentEvents(url, token, variables, onEvent, onClosed) {
1937
+ const socket = new WebSocket(url, WS_PROTOCOL);
1938
+ let stopped = false;
1939
+ const send = (message) => socket.send(JSON.stringify(message));
1940
+ socket.onopen = () => send({ type: 'connection_init', payload: { [TOKEN_HEADER]: token, headers: { [TOKEN_HEADER]: token } } });
1941
+ socket.onmessage = ({ data }) => {
1942
+ const message = JSON.parse(String(data));
1943
+ if (message.type === 'connection_ack') {
1944
+ send({ id: SUBSCRIPTION_ID, type: 'subscribe', payload: { query: EVENTS_QUERY, variables } });
1945
+ }
1946
+ else if (message.type === 'ping') {
1947
+ send({ type: 'pong' });
1948
+ }
1949
+ else if (message.type === 'next' && message.payload?.data?.agentEvents) {
1950
+ onEvent(message.payload.data.agentEvents);
1951
+ }
1952
+ else if (message.type === 'next' || message.type === 'error' || message.type === 'complete') {
1953
+ // Recusa do gateway (agente de outro client, credencial vencida) ou fim inesperado: não há mais o que esperar.
1954
+ stop();
1955
+ onClosed();
1956
+ }
1957
+ };
1958
+ socket.onclose = () => {
1959
+ if (!stopped) {
1960
+ stopped = true;
1961
+ onClosed();
1962
+ }
1963
+ };
1964
+ function stop() {
1965
+ stopped = true;
1966
+ if (socket.readyState === WebSocket.OPEN)
1967
+ send({ id: SUBSCRIPTION_ID, type: 'complete' });
1968
+ socket.close();
1969
+ }
1970
+ return stop;
1971
+ }
1972
+ /** Envia a mensagem. Falha de rede sobe como exceção; recusa do backend volta no envelope `{ status, message }`. */
1973
+ async function sendAgentMessage(url, token, variables, text) {
1974
+ const response = await fetch(url, {
1975
+ method: 'POST',
1976
+ headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
1977
+ body: JSON.stringify({
1978
+ query: SEND_MUTATION,
1979
+ variables: { agentId: variables.agentId, input: { conversationId: variables.conversationId, text } },
1980
+ }),
1981
+ });
1982
+ const body = await response.json();
1983
+ return body?.data?.sendAgentMessage ?? { status: response.status >= 400 ? response.status : 500, message: '' };
1984
+ }
1985
+ /** Avalia uma resposta, com uma correção opcional. `messageIndex` é a posição dela na conversa guardada. */
1986
+ async function rateAgentMessage(url, token, variables, messageIndex, rating, correction) {
1987
+ const response = await fetch(url, {
1988
+ method: 'POST',
1989
+ headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
1990
+ body: JSON.stringify({
1991
+ query: RATE_MUTATION,
1992
+ variables: { agentId: variables.agentId, input: { conversationId: variables.conversationId, messageIndex, rating, correction } },
1993
+ }),
1994
+ });
1995
+ const body = await response.json();
1996
+ return body?.data?.rateAgentMessage ?? { status: response.status >= 400 ? response.status : 500, message: '' };
1997
+ }
1998
+ /** Perguntas prontas que o agente oferece. Agente que não aparece na lista não tem sugestões. */
1999
+ async function fetchAgentSuggestions(url, token, agentId) {
2000
+ const response = await fetch(url, {
2001
+ method: 'POST',
2002
+ headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
2003
+ body: JSON.stringify({ query: AGENTS_QUERY }),
2004
+ });
2005
+ const body = await response.json();
2006
+ const agents = body?.data?.agents?.data ?? [];
2007
+ return agents.find((agent) => agent.id === agentId)?.suggestions ?? [];
2008
+ }
2009
+ /** As perguntas e respostas de uma conversa que já existe; vazia se ela expirou. */
2010
+ async function fetchAgentConversation(url, token, variables) {
2011
+ const response = await fetch(url, {
2012
+ method: 'POST',
2013
+ headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
2014
+ body: JSON.stringify({ query: CONVERSATION_QUERY, variables }),
2015
+ });
2016
+ const body = await response.json();
2017
+ return body?.data?.agentConversation?.data ?? [];
2018
+ }
2019
+
2020
+ // Teto para o agente dar sinal de vida; acima do limite de uma execução no backend.
2021
+ const SILENCE_LIMIT_MS = 200_000;
2022
+ const STORAGE_PREFIX = 'spine-ai-agent:conversation:';
2023
+ const PLACEMENT_KEY = 'spine-ai-agent:placement';
2024
+ // O gateway fecha a conexão quando o token vence, e a rede também cai: a resposta continua no backend. Reconecta com o token atual.
2025
+ const RECONNECT_ATTEMPTS = 5;
2026
+ const RECONNECT_DELAY_MS = 1_000;
2027
+ const RECOVERY_POLL_MS = 5_000;
2028
+ function readPlacement() {
2029
+ try {
2030
+ return JSON.parse(readStored(PLACEMENT_KEY) ?? '{}');
2031
+ }
2032
+ catch {
2033
+ return {};
2034
+ }
2035
+ }
2036
+ function readStored(key) {
2037
+ try {
2038
+ return localStorage.getItem(key);
2039
+ }
2040
+ catch {
2041
+ return null;
2042
+ }
2043
+ }
2044
+ function writeStored(key, value) {
2045
+ try {
2046
+ localStorage.setItem(key, value);
2047
+ }
2048
+ catch {
2049
+ // Navegador sem armazenamento: a conversa só não volta depois de recarregar.
2050
+ }
2051
+ }
2052
+ /**
2053
+ * Conversa com o agente do app (`SpineFeatures.aiAgent`). Fica no serviço para sobreviver a fechar e abrir o painel. O id
2054
+ * da conversa fica no navegador, só ele: as mensagens são do backend e voltam de lá ao recarregar a tela, e a chave lá
2055
+ * leva o usuário do token, então quem entrar depois nesse navegador não vê a conversa. A cada mensagem abre uma assinatura, espera o `ready` do gateway,
2056
+ * envia e acompanha até `done` ou `error`.
2057
+ *
2058
+ * Se a conexão cair depois do envio, o backend segue trabalhando: reconecta com o token atual, sem reenviar, e busca a resposta salva, porque os
2059
+ * eventos do intervalo se perderam e a resposta pode até já ter terminado.
2060
+ */
2061
+ class SpineAiAgentService {
2062
+ constructor() {
2063
+ this.config = inject(SPINE_CONFIG, { optional: true })?.features?.aiAgent ?? null;
2064
+ this.auth = inject(AuthorizationService);
2065
+ this.text = inject(SPINE_MESSAGES).aiAgentChat;
2066
+ this.enabled = !!this.config;
2067
+ this.title = this.config?.title || this.text.title;
2068
+ this.theme = inject(ThemeService);
2069
+ this.avatarSources = resolveSpineImage(this.config?.avatar, SPINE_DEFAULT_AGENT_AVATAR);
2070
+ this.thinkingSources = resolveSpineImage(this.config?.avatarThinking, null);
2071
+ this.imageSources = resolveSpineImage(this.config?.image, SPINE_DEFAULT_AGENT_IMAGE);
2072
+ /** Arquivos que não carregaram: saem, em vez de virar imagem quebrada na tela. */
2073
+ this.failedImages = signal(new Set(), ...(ngDevMode ? [{ debugName: "failedImages" }] : /* istanbul ignore next */ []));
2074
+ /** O rosto do agente, para o tema atual. */
2075
+ this.avatar = computed(() => this.pickImage(this.avatarSources), ...(ngDevMode ? [{ debugName: "avatar" }] : /* istanbul ignore next */ []));
2076
+ /** A figura do agente, para o tema atual. */
2077
+ this.image = computed(() => this.pickImage(this.imageSources), ...(ngDevMode ? [{ debugName: "image" }] : /* istanbul ignore next */ []));
2078
+ this.greetingTitle = this.config?.greeting?.title || this.text.greetingTitle;
2079
+ this.greetingText = this.config?.greeting?.text || this.text.greetingText;
2080
+ this.entries = signal([], ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
2081
+ this.running = signal(false, ...(ngDevMode ? [{ debugName: "running" }] : /* istanbul ignore next */ []));
2082
+ /** Perguntas prontas que o agente oferece, mostradas na conversa vazia. */
2083
+ this.suggestions = signal([], ...(ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []));
2084
+ /** O rosto de agora, que muda enquanto o agente trabalha. */
2085
+ this.avatarNow = computed(() => (this.running() ? this.pickImage(this.thinkingSources) : null) ?? this.avatar(), ...(ngDevMode ? [{ debugName: "avatarNow" }] : /* istanbul ignore next */ []));
2086
+ this.panelOpen = signal(false, ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
2087
+ /** O balão saiu da tela e virou um ícone no cabeçalho. */
2088
+ this.docked = signal(readPlacement().docked === true, ...(ngDevMode ? [{ debugName: "docked" }] : /* istanbul ignore next */ []));
2089
+ this.position = signal(readPlacement().position ?? null, ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
2090
+ /** A pessoa pediu o painel agora: a conversa recebe o foco. Ao recarregar a tela, não. */
2091
+ this.focusOnOpen = false;
2092
+ this.storageKey = `${STORAGE_PREFIX}${this.config?.agent ?? ''}`;
2093
+ this.storedConversation = readStored(this.storageKey);
2094
+ this.conversationId = this.storedConversation ?? this.newConversationId();
2095
+ this.restored = !this.storedConversation;
2096
+ this.nextId = 0;
2097
+ this.stop = null;
2098
+ this.silence = null;
2099
+ this.reconnectTimer = null;
2100
+ this.recoveryTimer = null;
2101
+ /** A mensagem já foi enviada: daqui em diante, a conexão pode cair sem perder a resposta. */
2102
+ this.messageSent = false;
2103
+ this.reconnects = 0;
2104
+ this.resumed = false;
2105
+ /** Mensagens que o backend já tinha guardado antes desta pergunta, para reconhecer a resposta nova. */
2106
+ this.savedBefore = 0;
2107
+ this.suggestionsLoaded = false;
2108
+ }
2109
+ /** Como no logo do header: no escuro vale `dark`, e se ele falhar volta para `light`. */
2110
+ pickImage(sources) {
2111
+ if (!this.config || !sources)
2112
+ return null;
2113
+ const failed = this.failedImages();
2114
+ if (this.theme.current === 'dark' && sources.dark && !failed.has(sources.dark))
2115
+ return sources.dark;
2116
+ return failed.has(sources.light) ? null : sources.light;
2117
+ }
2118
+ imageFailed(src) {
2119
+ if (!this.failedImages().has(src))
2120
+ this.failedImages.update((failed) => new Set(failed).add(src));
2121
+ }
2122
+ togglePanel() {
2123
+ this.focusOnOpen = !this.panelOpen();
2124
+ this.panelOpen.set(this.focusOnOpen);
2125
+ }
2126
+ dock() {
2127
+ this.docked.set(true);
2128
+ this.savePlacement();
2129
+ }
2130
+ undock() {
2131
+ this.docked.set(false);
2132
+ this.savePlacement();
2133
+ }
2134
+ savePlacement() {
2135
+ writeStored(PLACEMENT_KEY, JSON.stringify({ docked: this.docked(), position: this.position() }));
2136
+ }
2137
+ /** Abre o painel e pergunta, para quem escolheu uma sugestão no convite. */
2138
+ askFromGreeting(question) {
2139
+ this.focusOnOpen = true;
2140
+ this.panelOpen.set(true);
2141
+ this.restored = true;
2142
+ this.send(question);
2143
+ }
2144
+ /** Busca as sugestões uma vez. Sem elas o chat funciona igual, então a falha não aparece para a pessoa. */
2145
+ loadSuggestions() {
2146
+ const token = this.auth.getToken();
2147
+ if (!this.config || !token || this.suggestionsLoaded)
2148
+ return;
2149
+ this.suggestionsLoaded = true;
2150
+ fetchAgentSuggestions(this.config.http, token, this.config.agent).then((suggestions) => this.suggestions.set(suggestions), () => { this.suggestionsLoaded = false; });
2151
+ }
2152
+ /** Traz de volta a conversa guardada, uma vez. Se falhar, ou se ela expirou, o chat só começa vazio. */
2153
+ restoreConversation() {
2154
+ const token = this.auth.getToken();
2155
+ if (!this.config || !token || this.restored)
2156
+ return;
2157
+ this.restored = true;
2158
+ const conversationId = this.conversationId;
2159
+ fetchAgentConversation(this.config.http, token, { agentId: this.config.agent, conversationId }).then((messages) => {
2160
+ // A pessoa pode ter começado a escrever antes de a resposta chegar: o que está na tela vence.
2161
+ if (this.entries().length || conversationId !== this.conversationId)
2162
+ return;
2163
+ for (const message of messages) {
2164
+ const id = this.append(message.role, message.text);
2165
+ const steps = this.storedSteps(message.steps);
2166
+ if (steps.length)
2167
+ this.update(id, (entry) => ({ ...entry, steps }));
2168
+ }
2169
+ }, () => { this.restored = false; });
2170
+ }
2171
+ /** Começa outra conversa; a anterior expira sozinha no backend. */
2172
+ reset() {
2173
+ this.finish();
2174
+ this.entries.set([]);
2175
+ this.restored = true;
2176
+ this.conversationId = this.newConversationId();
2177
+ }
2178
+ /** Avalia uma resposta. A posição conta só o que o backend guardou: resposta que falhou não entra no histórico dele. */
2179
+ async rate(entryId, rating, correction = '') {
2180
+ const token = this.auth.getToken();
2181
+ const saved = this.entries().filter((entry) => !entry.error);
2182
+ const index = saved.findIndex((entry) => entry.id === entryId);
2183
+ if (!this.config || !token || index < 1)
2184
+ return false;
2185
+ const variables = { agentId: this.config.agent, conversationId: this.conversationId };
2186
+ try {
2187
+ const { status } = await rateAgentMessage(this.config.http, token, variables, index, rating, correction);
2188
+ if (status >= 400)
2189
+ return false;
2190
+ }
2191
+ catch {
2192
+ return false;
2193
+ }
2194
+ this.update(entryId, (entry) => ({ ...entry, rating }));
2195
+ return true;
2196
+ }
2197
+ /** Repete a última pergunta depois de um erro. Resposta que falhou não entra no histórico do backend. */
2198
+ retry() {
2199
+ const entries = this.entries();
2200
+ const question = entries[entries.length - 2];
2201
+ if (this.running() || !entries[entries.length - 1]?.error || question?.role !== 'person')
2202
+ return;
2203
+ this.entries.set(entries.slice(0, -2));
2204
+ this.send(question.text);
2205
+ }
2206
+ send(raw) {
2207
+ const text = raw.trim();
2208
+ const token = this.auth.getToken();
2209
+ if (!this.config || !text || this.running())
2210
+ return;
2211
+ this.savedBefore = this.entries().filter((entry) => !entry.error).length;
2212
+ this.append('person', text);
2213
+ const answerId = this.append('agent', '');
2214
+ if (!token) {
2215
+ this.fail(answerId, this.text.sendError);
2216
+ return;
2217
+ }
2218
+ this.running.set(true);
2219
+ this.listen(answerId, { agentId: this.config.agent, conversationId: this.conversationId }, text);
2220
+ }
2221
+ listen(answerId, variables, question) {
2222
+ const token = this.auth.getToken();
2223
+ if (!this.config || !token) {
2224
+ this.fail(answerId, this.text.connectionError);
2225
+ return;
2226
+ }
2227
+ const http = this.config.http;
2228
+ this.watchSilence(answerId);
2229
+ this.stop = subscribeAgentEvents(this.config.ws, token, variables, (event) => {
2230
+ this.watchSilence(answerId);
2231
+ if (event.type === 'ready') {
2232
+ this.onReady(answerId, variables, question, http, token);
2233
+ }
2234
+ else if (event.type === 'done' && this.resumed) {
2235
+ void this.loadAnswer(answerId, variables, question).finally(() => this.finish());
2236
+ }
2237
+ else {
2238
+ this.apply(answerId, event);
2239
+ }
2240
+ }, () => this.onDisconnected(answerId, variables, question));
2241
+ }
2242
+ onReady(answerId, variables, question, http, token) {
2243
+ if (this.messageSent) {
2244
+ this.reconnects = 0;
2245
+ this.resumed = true;
2246
+ void this.pollAnswer(answerId, variables, question);
2247
+ return;
2248
+ }
2249
+ this.messageSent = true;
2250
+ sendAgentMessage(http, token, variables, question).then(({ status, message }) => { if (status >= 400)
2251
+ this.fail(answerId, message || this.text.sendError); }, () => this.fail(answerId, this.text.sendError));
2252
+ }
2253
+ onDisconnected(answerId, variables, question) {
2254
+ if (!this.running())
2255
+ return;
2256
+ // Antes do envio, ou depois de esgotar as tentativas, ninguém está trabalhando nesta resposta ou não há como acompanhar.
2257
+ if (!this.messageSent || this.reconnects >= RECONNECT_ATTEMPTS) {
2258
+ this.fail(answerId, this.text.connectionError);
2259
+ return;
2260
+ }
2261
+ this.reconnects += 1;
2262
+ this.stop = null;
2263
+ this.reconnectTimer = setTimeout(() => this.listen(answerId, variables, question), RECONNECT_DELAY_MS * this.reconnects);
2264
+ }
2265
+ async pollAnswer(answerId, variables, question) {
2266
+ if (await this.loadAnswer(answerId, variables, question)) {
2267
+ this.finish();
2268
+ }
2269
+ else if (this.running()) {
2270
+ this.recoveryTimer = setTimeout(() => void this.pollAnswer(answerId, variables, question), RECOVERY_POLL_MS);
2271
+ }
2272
+ }
2273
+ /** Traz a resposta salva, se ela já terminou. Serve também para repor o que se perdeu enquanto a conexão estava fora. */
2274
+ async loadAnswer(answerId, variables, question) {
2275
+ const token = this.auth.getToken();
2276
+ if (!this.config || !token)
2277
+ return false;
2278
+ try {
2279
+ const messages = await fetchAgentConversation(this.config.http, token, variables);
2280
+ const answer = messages[messages.length - 1];
2281
+ const asked = messages[messages.length - 2];
2282
+ if (answer?.role !== 'agent' || asked?.text !== question || messages.length <= this.savedBefore)
2283
+ return false;
2284
+ const steps = this.storedSteps(answer.steps);
2285
+ this.update(answerId, (entry) => ({ ...entry, text: answer.text, steps: steps.length ? steps : entry.steps }));
2286
+ return true;
2287
+ }
2288
+ catch {
2289
+ return false;
2290
+ }
2291
+ }
2292
+ storedSteps(steps) {
2293
+ return (steps ?? []).map((step) => ({
2294
+ id: ++this.nextId, tool: step.tool, title: step.title, status: step.ok ? 'done' : 'error', detail: step.detail ?? undefined,
2295
+ }));
2296
+ }
2297
+ newConversationId() {
2298
+ const id = crypto.randomUUID();
2299
+ writeStored(this.storageKey, id);
2300
+ return id;
2301
+ }
2302
+ apply(answerId, event) {
2303
+ switch (event.type) {
2304
+ case 'text_delta':
2305
+ this.update(answerId, (entry) => ({ ...entry, text: entry.text + (event.text ?? '') }));
2306
+ break;
2307
+ case 'text_replace':
2308
+ this.update(answerId, (entry) => ({ ...entry, text: event.text ?? '' }));
2309
+ break;
2310
+ case 'tool_started': {
2311
+ const tool = event.tool ?? '';
2312
+ const step = { id: ++this.nextId, tool, title: event.title || tool, status: 'running' };
2313
+ this.update(answerId, (entry) => ({ ...entry, steps: [...entry.steps, step] }));
2314
+ break;
2315
+ }
2316
+ case 'tool_finished':
2317
+ this.finishStep(answerId, event.tool ?? '', event.ok === false ? 'error' : 'done', event.detail ?? undefined);
2318
+ break;
2319
+ case 'done':
2320
+ this.finish();
2321
+ break;
2322
+ case 'error':
2323
+ this.fail(answerId, event.message || this.text.sendError);
2324
+ break;
2325
+ }
2326
+ }
2327
+ /** A mesma ferramenta pode rodar mais de uma vez: encerra a chamada mais antiga que ainda está em andamento. */
2328
+ finishStep(answerId, tool, status, detail) {
2329
+ this.update(answerId, (entry) => {
2330
+ const index = entry.steps.findIndex((step) => step.tool === tool && step.status === 'running');
2331
+ return { ...entry, steps: entry.steps.map((step, i) => (i === index ? { ...step, status, detail } : step)) };
2332
+ });
2333
+ }
2334
+ fail(answerId, message) {
2335
+ this.update(answerId, (entry) => ({
2336
+ ...entry,
2337
+ text: message,
2338
+ error: true,
2339
+ steps: entry.steps.map((step) => (step.status === 'running' ? { ...step, status: 'error' } : step)),
2340
+ }));
2341
+ this.finish();
2342
+ }
2343
+ finish() {
2344
+ if (this.silence)
2345
+ clearTimeout(this.silence);
2346
+ if (this.reconnectTimer)
2347
+ clearTimeout(this.reconnectTimer);
2348
+ if (this.recoveryTimer)
2349
+ clearTimeout(this.recoveryTimer);
2350
+ this.silence = this.reconnectTimer = this.recoveryTimer = null;
2351
+ this.messageSent = this.resumed = false;
2352
+ this.reconnects = 0;
2353
+ this.running.set(false);
2354
+ const stop = this.stop;
2355
+ this.stop = null;
2356
+ stop?.();
2357
+ }
2358
+ watchSilence(answerId) {
2359
+ if (this.silence)
2360
+ clearTimeout(this.silence);
2361
+ this.silence = setTimeout(() => this.fail(answerId, this.text.connectionError), SILENCE_LIMIT_MS);
2362
+ }
2363
+ append(role, text) {
2364
+ const id = ++this.nextId;
2365
+ this.entries.update((entries) => [...entries, { id, role, text, steps: [], error: false }]);
2366
+ return id;
2367
+ }
2368
+ update(id, change) {
2369
+ this.entries.update((entries) => entries.map((entry) => (entry.id === id ? change(entry) : entry)));
2370
+ }
2371
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2372
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentService, providedIn: 'root' }); }
2373
+ }
2374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentService, decorators: [{
2375
+ type: Injectable,
2376
+ args: [{ providedIn: 'root' }]
2377
+ }] });
2378
+
2379
+ const SPINE_AGENT_DOCK_CLASS = 'spine-agent-dock';
2380
+ /** O agente no cabeçalho, ao lado do sino: só aparece depois que a pessoa tira o balão da tela. */
2381
+ class SpineAiAgentDockButtonComponent {
2382
+ constructor() {
2383
+ this.agent = inject(SpineAiAgentService);
2384
+ this.text = inject(SPINE_MESSAGES).aiAgentChat;
2385
+ this.dockClass = SPINE_AGENT_DOCK_CLASS;
2386
+ }
2387
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentDockButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2388
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SpineAiAgentDockButtonComponent, isStandalone: true, selector: "spine-ai-agent-dock-button", ngImport: i0, template: `
2389
+ @if (agent.enabled && agent.docked()) {
2390
+ <hm-icon-button
2391
+ [class]="dockClass"
2392
+ emphasis="subtle"
2393
+ icon="chat-bubble"
2394
+ [label]="agent.panelOpen() ? text.close : agent.title"
2395
+ (clicked)="agent.togglePanel()"
2396
+ />
2397
+ }
2398
+ `, isInline: true, dependencies: [{ kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2399
+ }
2400
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentDockButtonComponent, decorators: [{
2401
+ type: Component,
2402
+ args: [{
2403
+ selector: 'spine-ai-agent-dock-button',
2404
+ imports: [IconButtonComponent],
2405
+ changeDetection: ChangeDetectionStrategy.OnPush,
2406
+ template: `
2407
+ @if (agent.enabled && agent.docked()) {
2408
+ <hm-icon-button
2409
+ [class]="dockClass"
2410
+ emphasis="subtle"
2411
+ icon="chat-bubble"
2412
+ [label]="agent.panelOpen() ? text.close : agent.title"
2413
+ (clicked)="agent.togglePanel()"
2414
+ />
2415
+ }
2416
+ `,
2417
+ }]
2418
+ }] });
2419
+
1859
2420
  /** Tempo do aviso de falha na tela (segundos). */
1860
2421
  const LOGOUT_FAILED_SECONDS = 8;
1861
2422
  /**
@@ -2415,7 +2976,7 @@ class HeaderComponent {
2415
2976
  catch { /* storage cheio/indisponível */ }
2416
2977
  }
2417
2978
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HeaderComponent, deps: [{ token: i1.AuthorizationService }, { token: i2$1.ThemeService }, { token: i2$1.ThemePresetService }, { token: i2$1.DensityService }, { token: i1.ViewService }, { token: i3.Router }, { token: ShortcutService }, { token: SPINE_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
2418
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: HeaderComponent, isStandalone: true, selector: "spine-header", ngImport: i0, template: "<hm-header [class.header-blocked]=\"headerBlocked\" (searchRequested)=\"onSearchRequested()\">\n <div *ngIf=\"headerBlocked\" class=\"header-overlay\"></div>\n\n <spine-app-launcher></spine-app-launcher>\n\n <!-- Marca: logo do app (SpineConfig.logo, padr\u00E3o assets/images/logo.svg) + SpineConfig.appName,\n levando \u00E0 primeira rota naveg\u00E1vel. Ver SpineBrandComponent. -->\n <spine-brand [homeRoute]=\"homeRoute\" [label]=\"text.home\" />\n\n <div actions class=\"h-right\">\n <!-- central de notifica\u00E7\u00F5es: o sino, com as novidades das fontes registradas (sem fonte, n\u00E3o aparece) -->\n <spine-inbox-button />\n\n <!-- tema claro/escuro -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setTheme(themeService.current == 'light' ? 'dark' : 'light')\"\n [icon]=\"themeService.current == 'light' ? 'moon' : 'sun'\"\n [label]=\"themeService.current == 'light' ? text.themeDark : text.themeLight\">\n </hm-icon-button>\n\n <!-- densidade -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setDensity()\"\n [icon]=\"isCompactDensity ? 'density-comfortable' : 'density-compact'\"\n [label]=\"isCompactDensity ? text.densityComfortable : text.densityCompact\">\n </hm-icon-button>\n\n <!-- Avaliar o {app} (SpineFeatures.ratings): a estrela abre o di\u00E1logo e fica cheia depois de avaliar. Toma o\n lugar do bot\u00E3o de tela cheia (o atalho de teclado continua); sem avalia\u00E7\u00E3o, a tela cheia volta aqui. -->\n @if (showFullscreenButton) {\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"toggleFullscreen()\"\n [icon]=\"isFullscreen ? 'shrink' : 'resize'\"\n [label]=\"isFullscreen ? text.exitFullscreen : text.fullscreen\">\n </hm-icon-button>\n } @else {\n <spine-rate-app-button />\n }\n\n <span class=\"h-divider\" aria-hidden=\"true\"></span>\n\n <!-- perfil: crach\u00E1, ajuda, tema e sair ficam dentro do menu -->\n <hm-menu-button class=\"h-profile\" [label]=\"profile.name ?? text.user\" align=\"end\" [items]=\"profileMenuItems\" (chosen)=\"onProfileMenuChosen($event)\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\"></hm-account-avatar>\n <span class=\"h-profile-info\">\n <span class=\"h-profile-name\">{{ profile.name ?? text.user }}</span>\n <span class=\"h-profile-role\" *ngIf=\"badge\">{{ badge?.name }}</span>\n </span>\n </hm-menu-button>\n </div>\n</hm-header>\n\n<!-- Busca global: paleta ancorada logo abaixo do cabe\u00E7alho. Campo vazio = favoritos, abertos h\u00E1 pouco e\n m\u00F3dulos; com texto, busca em todas as telas. Setas/Enter navegam, Esc fecha. -->\n<hm-command-palette\n [(open)]=\"quickOpen\"\n [commands]=\"searchCommands\"\n [browseGroups]=\"searchBrowse\"\n [groupIcons]=\"searchIcons\"\n [label]=\"text.search\"\n [placeholder]=\"text.searchPlaceholder\"\n/>\n\n<!-- A janela de avalia\u00E7\u00E3o (flutuante, canto inferior direito): \"Avaliar o {app}\" da estrela do cabe\u00E7alho e de\n Configura\u00E7\u00F5es \u203A Sobre, e \"Avaliar esta tela\" da barra da p\u00E1gina (SpineFeatures.ratings). S\u00F3 desenha com o recurso\n ligado e aberta. -->\n<spine-rating-window />\n", styles: ["@charset \"UTF-8\";:host{display:contents}.header-overlay{position:absolute;inset:0;z-index:9999;cursor:not-allowed}.h-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:40px;min-width:40px;padding:0 10px;border:none;background:transparent;cursor:pointer;border-radius:9px;text-decoration:none;color:var(--hm-navbar-text);outline:none}.h-btn:focus-visible,.h-btn:hover{background:var(--hm-navbar-hover)}.h-btn.active{background:var(--hm-navbar-hover)}.h-right{display:flex;align-items:center;gap:var(--hm-header-action-gap)}.h-divider{width:1px;height:24px;margin:0 4px;background:var(--hm-navbar-hover)}.h-profile{display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 4px 0 2px}.h-profile hm-account-avatar{background:color-mix(in srgb,var(--hm-navbar-text) 18%,transparent);color:var(--hm-navbar-text)}.h-profile-info{display:flex;flex-direction:column;min-width:0;max-width:140px;line-height:1.2;text-align:left}.h-profile-name{font-size:var(--hm-font-size-label);font-weight:var(--hm-font-weight-semibold);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.h-profile-role{color:color-mix(in srgb,var(--hm-navbar-text) 65%,transparent);font-size:var(--hm-font-size-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: HeaderComponent$1, selector: "hm-header", inputs: ["searchPlaceholder", "searchLabel"], outputs: ["searchRequested"] }, { kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }, { kind: "component", type: MenuButtonComponent, selector: "hm-menu-button", inputs: ["label", "icon", "items", "showArrow", "align", "side", "size", "shape", "layer", "closeOnCheck", "disabled"], outputs: ["chosen", "checkedChange"] }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading", "presence", "texts"] }, { kind: "component", type: CommandPaletteComponent, selector: "hm-command-palette", inputs: ["open", "commands", "shortcut", "label", "placeholder", "recentLimit", "recentIds", "closeOnRun", "browseGroups", "groupIcons"], outputs: ["openChange", "recentIdsChange", "executed"] }, { kind: "component", type: AppLauncherComponent, selector: "spine-app-launcher" }, { kind: "component", type: SpineBrandComponent, selector: "spine-brand", inputs: ["homeRoute", "label"] }, { kind: "component", type: SpineRatingWindowComponent, selector: "spine-rating-window" }, { kind: "component", type: SpineRateAppButtonComponent, selector: "spine-rate-app-button" }, { kind: "component", type: SpineInboxButtonComponent, selector: "spine-inbox-button" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2979
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: HeaderComponent, isStandalone: true, selector: "spine-header", ngImport: i0, template: "<hm-header [class.header-blocked]=\"headerBlocked\" (searchRequested)=\"onSearchRequested()\">\n <div *ngIf=\"headerBlocked\" class=\"header-overlay\"></div>\n\n <spine-app-launcher></spine-app-launcher>\n\n <!-- Marca: logo do app (SpineConfig.logo, padr\u00E3o assets/images/logo.svg) + SpineConfig.appName,\n levando \u00E0 primeira rota naveg\u00E1vel. Ver SpineBrandComponent. -->\n <spine-brand [homeRoute]=\"homeRoute\" [label]=\"text.home\" />\n\n <div actions class=\"h-right\">\n <!-- central de notifica\u00E7\u00F5es: o sino, com as novidades das fontes registradas (sem fonte, n\u00E3o aparece) -->\n <spine-inbox-button />\n\n <!-- o agente de IA, depois que a pessoa tira o bal\u00E3o da tela -->\n <spine-ai-agent-dock-button />\n\n <!-- tema claro/escuro -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setTheme(themeService.current == 'light' ? 'dark' : 'light')\"\n [icon]=\"themeService.current == 'light' ? 'moon' : 'sun'\"\n [label]=\"themeService.current == 'light' ? text.themeDark : text.themeLight\">\n </hm-icon-button>\n\n <!-- densidade -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setDensity()\"\n [icon]=\"isCompactDensity ? 'density-comfortable' : 'density-compact'\"\n [label]=\"isCompactDensity ? text.densityComfortable : text.densityCompact\">\n </hm-icon-button>\n\n <!-- Avaliar o {app} (SpineFeatures.ratings): a estrela abre o di\u00E1logo e fica cheia depois de avaliar. Toma o\n lugar do bot\u00E3o de tela cheia (o atalho de teclado continua); sem avalia\u00E7\u00E3o, a tela cheia volta aqui. -->\n @if (showFullscreenButton) {\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"toggleFullscreen()\"\n [icon]=\"isFullscreen ? 'shrink' : 'resize'\"\n [label]=\"isFullscreen ? text.exitFullscreen : text.fullscreen\">\n </hm-icon-button>\n } @else {\n <spine-rate-app-button />\n }\n\n <span class=\"h-divider\" aria-hidden=\"true\"></span>\n\n <!-- perfil: crach\u00E1, ajuda, tema e sair ficam dentro do menu -->\n <hm-menu-button class=\"h-profile\" [label]=\"profile.name ?? text.user\" align=\"end\" [items]=\"profileMenuItems\" (chosen)=\"onProfileMenuChosen($event)\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\"></hm-account-avatar>\n <span class=\"h-profile-info\">\n <span class=\"h-profile-name\">{{ profile.name ?? text.user }}</span>\n <span class=\"h-profile-role\" *ngIf=\"badge\">{{ badge?.name }}</span>\n </span>\n </hm-menu-button>\n </div>\n</hm-header>\n\n<!-- Busca global: paleta ancorada logo abaixo do cabe\u00E7alho. Campo vazio = favoritos, abertos h\u00E1 pouco e\n m\u00F3dulos; com texto, busca em todas as telas. Setas/Enter navegam, Esc fecha. -->\n<hm-command-palette\n [(open)]=\"quickOpen\"\n [commands]=\"searchCommands\"\n [browseGroups]=\"searchBrowse\"\n [groupIcons]=\"searchIcons\"\n [label]=\"text.search\"\n [placeholder]=\"text.searchPlaceholder\"\n/>\n\n<!-- A janela de avalia\u00E7\u00E3o (flutuante, canto inferior direito): \"Avaliar o {app}\" da estrela do cabe\u00E7alho e de\n Configura\u00E7\u00F5es \u203A Sobre, e \"Avaliar esta tela\" da barra da p\u00E1gina (SpineFeatures.ratings). S\u00F3 desenha com o recurso\n ligado e aberta. -->\n<spine-rating-window />\n", styles: ["@charset \"UTF-8\";:host{display:contents}.header-overlay{position:absolute;inset:0;z-index:9999;cursor:not-allowed}.h-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:40px;min-width:40px;padding:0 10px;border:none;background:transparent;cursor:pointer;border-radius:9px;text-decoration:none;color:var(--hm-navbar-text);outline:none}.h-btn:focus-visible,.h-btn:hover{background:var(--hm-navbar-hover)}.h-btn.active{background:var(--hm-navbar-hover)}.h-right{display:flex;align-items:center;gap:var(--hm-header-action-gap)}.h-divider{width:1px;height:24px;margin:0 4px;background:var(--hm-navbar-hover)}.h-profile{display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 4px 0 2px}.h-profile hm-account-avatar{background:color-mix(in srgb,var(--hm-navbar-text) 18%,transparent);color:var(--hm-navbar-text)}.h-profile-info{display:flex;flex-direction:column;min-width:0;max-width:140px;line-height:1.2;text-align:left}.h-profile-name{font-size:var(--hm-font-size-label);font-weight:var(--hm-font-weight-semibold);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.h-profile-role{color:color-mix(in srgb,var(--hm-navbar-text) 65%,transparent);font-size:var(--hm-font-size-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: HeaderComponent$1, selector: "hm-header", inputs: ["searchPlaceholder", "searchLabel"], outputs: ["searchRequested"] }, { kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }, { kind: "component", type: MenuButtonComponent, selector: "hm-menu-button", inputs: ["label", "icon", "items", "showArrow", "align", "side", "size", "shape", "layer", "closeOnCheck", "disabled"], outputs: ["chosen", "checkedChange"] }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading", "presence", "texts"] }, { kind: "component", type: CommandPaletteComponent, selector: "hm-command-palette", inputs: ["open", "commands", "shortcut", "label", "placeholder", "recentLimit", "recentIds", "closeOnRun", "browseGroups", "groupIcons"], outputs: ["openChange", "recentIdsChange", "executed"] }, { kind: "component", type: AppLauncherComponent, selector: "spine-app-launcher" }, { kind: "component", type: SpineBrandComponent, selector: "spine-brand", inputs: ["homeRoute", "label"] }, { kind: "component", type: SpineRatingWindowComponent, selector: "spine-rating-window" }, { kind: "component", type: SpineRateAppButtonComponent, selector: "spine-rate-app-button" }, { kind: "component", type: SpineInboxButtonComponent, selector: "spine-inbox-button" }, { kind: "component", type: SpineAiAgentDockButtonComponent, selector: "spine-ai-agent-dock-button" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2419
2980
  }
2420
2981
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HeaderComponent, decorators: [{
2421
2982
  type: Component,
@@ -2423,7 +2984,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
2423
2984
  CommonModule,
2424
2985
  HeaderComponent$1, IconButtonComponent, IconComponent, MenuButtonComponent, AccountAvatarComponent, CommandPaletteComponent,
2425
2986
  AppLauncherComponent, SpineBrandComponent, SpineRatingWindowComponent, SpineRateAppButtonComponent, SpineInboxButtonComponent,
2426
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<hm-header [class.header-blocked]=\"headerBlocked\" (searchRequested)=\"onSearchRequested()\">\n <div *ngIf=\"headerBlocked\" class=\"header-overlay\"></div>\n\n <spine-app-launcher></spine-app-launcher>\n\n <!-- Marca: logo do app (SpineConfig.logo, padr\u00E3o assets/images/logo.svg) + SpineConfig.appName,\n levando \u00E0 primeira rota naveg\u00E1vel. Ver SpineBrandComponent. -->\n <spine-brand [homeRoute]=\"homeRoute\" [label]=\"text.home\" />\n\n <div actions class=\"h-right\">\n <!-- central de notifica\u00E7\u00F5es: o sino, com as novidades das fontes registradas (sem fonte, n\u00E3o aparece) -->\n <spine-inbox-button />\n\n <!-- tema claro/escuro -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setTheme(themeService.current == 'light' ? 'dark' : 'light')\"\n [icon]=\"themeService.current == 'light' ? 'moon' : 'sun'\"\n [label]=\"themeService.current == 'light' ? text.themeDark : text.themeLight\">\n </hm-icon-button>\n\n <!-- densidade -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setDensity()\"\n [icon]=\"isCompactDensity ? 'density-comfortable' : 'density-compact'\"\n [label]=\"isCompactDensity ? text.densityComfortable : text.densityCompact\">\n </hm-icon-button>\n\n <!-- Avaliar o {app} (SpineFeatures.ratings): a estrela abre o di\u00E1logo e fica cheia depois de avaliar. Toma o\n lugar do bot\u00E3o de tela cheia (o atalho de teclado continua); sem avalia\u00E7\u00E3o, a tela cheia volta aqui. -->\n @if (showFullscreenButton) {\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"toggleFullscreen()\"\n [icon]=\"isFullscreen ? 'shrink' : 'resize'\"\n [label]=\"isFullscreen ? text.exitFullscreen : text.fullscreen\">\n </hm-icon-button>\n } @else {\n <spine-rate-app-button />\n }\n\n <span class=\"h-divider\" aria-hidden=\"true\"></span>\n\n <!-- perfil: crach\u00E1, ajuda, tema e sair ficam dentro do menu -->\n <hm-menu-button class=\"h-profile\" [label]=\"profile.name ?? text.user\" align=\"end\" [items]=\"profileMenuItems\" (chosen)=\"onProfileMenuChosen($event)\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\"></hm-account-avatar>\n <span class=\"h-profile-info\">\n <span class=\"h-profile-name\">{{ profile.name ?? text.user }}</span>\n <span class=\"h-profile-role\" *ngIf=\"badge\">{{ badge?.name }}</span>\n </span>\n </hm-menu-button>\n </div>\n</hm-header>\n\n<!-- Busca global: paleta ancorada logo abaixo do cabe\u00E7alho. Campo vazio = favoritos, abertos h\u00E1 pouco e\n m\u00F3dulos; com texto, busca em todas as telas. Setas/Enter navegam, Esc fecha. -->\n<hm-command-palette\n [(open)]=\"quickOpen\"\n [commands]=\"searchCommands\"\n [browseGroups]=\"searchBrowse\"\n [groupIcons]=\"searchIcons\"\n [label]=\"text.search\"\n [placeholder]=\"text.searchPlaceholder\"\n/>\n\n<!-- A janela de avalia\u00E7\u00E3o (flutuante, canto inferior direito): \"Avaliar o {app}\" da estrela do cabe\u00E7alho e de\n Configura\u00E7\u00F5es \u203A Sobre, e \"Avaliar esta tela\" da barra da p\u00E1gina (SpineFeatures.ratings). S\u00F3 desenha com o recurso\n ligado e aberta. -->\n<spine-rating-window />\n", styles: ["@charset \"UTF-8\";:host{display:contents}.header-overlay{position:absolute;inset:0;z-index:9999;cursor:not-allowed}.h-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:40px;min-width:40px;padding:0 10px;border:none;background:transparent;cursor:pointer;border-radius:9px;text-decoration:none;color:var(--hm-navbar-text);outline:none}.h-btn:focus-visible,.h-btn:hover{background:var(--hm-navbar-hover)}.h-btn.active{background:var(--hm-navbar-hover)}.h-right{display:flex;align-items:center;gap:var(--hm-header-action-gap)}.h-divider{width:1px;height:24px;margin:0 4px;background:var(--hm-navbar-hover)}.h-profile{display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 4px 0 2px}.h-profile hm-account-avatar{background:color-mix(in srgb,var(--hm-navbar-text) 18%,transparent);color:var(--hm-navbar-text)}.h-profile-info{display:flex;flex-direction:column;min-width:0;max-width:140px;line-height:1.2;text-align:left}.h-profile-name{font-size:var(--hm-font-size-label);font-weight:var(--hm-font-weight-semibold);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.h-profile-role{color:color-mix(in srgb,var(--hm-navbar-text) 65%,transparent);font-size:var(--hm-font-size-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
2987
+ SpineAiAgentDockButtonComponent,
2988
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<hm-header [class.header-blocked]=\"headerBlocked\" (searchRequested)=\"onSearchRequested()\">\n <div *ngIf=\"headerBlocked\" class=\"header-overlay\"></div>\n\n <spine-app-launcher></spine-app-launcher>\n\n <!-- Marca: logo do app (SpineConfig.logo, padr\u00E3o assets/images/logo.svg) + SpineConfig.appName,\n levando \u00E0 primeira rota naveg\u00E1vel. Ver SpineBrandComponent. -->\n <spine-brand [homeRoute]=\"homeRoute\" [label]=\"text.home\" />\n\n <div actions class=\"h-right\">\n <!-- central de notifica\u00E7\u00F5es: o sino, com as novidades das fontes registradas (sem fonte, n\u00E3o aparece) -->\n <spine-inbox-button />\n\n <!-- o agente de IA, depois que a pessoa tira o bal\u00E3o da tela -->\n <spine-ai-agent-dock-button />\n\n <!-- tema claro/escuro -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setTheme(themeService.current == 'light' ? 'dark' : 'light')\"\n [icon]=\"themeService.current == 'light' ? 'moon' : 'sun'\"\n [label]=\"themeService.current == 'light' ? text.themeDark : text.themeLight\">\n </hm-icon-button>\n\n <!-- densidade -->\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"setDensity()\"\n [icon]=\"isCompactDensity ? 'density-comfortable' : 'density-compact'\"\n [label]=\"isCompactDensity ? text.densityComfortable : text.densityCompact\">\n </hm-icon-button>\n\n <!-- Avaliar o {app} (SpineFeatures.ratings): a estrela abre o di\u00E1logo e fica cheia depois de avaliar. Toma o\n lugar do bot\u00E3o de tela cheia (o atalho de teclado continua); sem avalia\u00E7\u00E3o, a tela cheia volta aqui. -->\n @if (showFullscreenButton) {\n <hm-icon-button emphasis=\"subtle\" (clicked)=\"toggleFullscreen()\"\n [icon]=\"isFullscreen ? 'shrink' : 'resize'\"\n [label]=\"isFullscreen ? text.exitFullscreen : text.fullscreen\">\n </hm-icon-button>\n } @else {\n <spine-rate-app-button />\n }\n\n <span class=\"h-divider\" aria-hidden=\"true\"></span>\n\n <!-- perfil: crach\u00E1, ajuda, tema e sair ficam dentro do menu -->\n <hm-menu-button class=\"h-profile\" [label]=\"profile.name ?? text.user\" align=\"end\" [items]=\"profileMenuItems\" (chosen)=\"onProfileMenuChosen($event)\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\"></hm-account-avatar>\n <span class=\"h-profile-info\">\n <span class=\"h-profile-name\">{{ profile.name ?? text.user }}</span>\n <span class=\"h-profile-role\" *ngIf=\"badge\">{{ badge?.name }}</span>\n </span>\n </hm-menu-button>\n </div>\n</hm-header>\n\n<!-- Busca global: paleta ancorada logo abaixo do cabe\u00E7alho. Campo vazio = favoritos, abertos h\u00E1 pouco e\n m\u00F3dulos; com texto, busca em todas as telas. Setas/Enter navegam, Esc fecha. -->\n<hm-command-palette\n [(open)]=\"quickOpen\"\n [commands]=\"searchCommands\"\n [browseGroups]=\"searchBrowse\"\n [groupIcons]=\"searchIcons\"\n [label]=\"text.search\"\n [placeholder]=\"text.searchPlaceholder\"\n/>\n\n<!-- A janela de avalia\u00E7\u00E3o (flutuante, canto inferior direito): \"Avaliar o {app}\" da estrela do cabe\u00E7alho e de\n Configura\u00E7\u00F5es \u203A Sobre, e \"Avaliar esta tela\" da barra da p\u00E1gina (SpineFeatures.ratings). S\u00F3 desenha com o recurso\n ligado e aberta. -->\n<spine-rating-window />\n", styles: ["@charset \"UTF-8\";:host{display:contents}.header-overlay{position:absolute;inset:0;z-index:9999;cursor:not-allowed}.h-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:40px;min-width:40px;padding:0 10px;border:none;background:transparent;cursor:pointer;border-radius:9px;text-decoration:none;color:var(--hm-navbar-text);outline:none}.h-btn:focus-visible,.h-btn:hover{background:var(--hm-navbar-hover)}.h-btn.active{background:var(--hm-navbar-hover)}.h-right{display:flex;align-items:center;gap:var(--hm-header-action-gap)}.h-divider{width:1px;height:24px;margin:0 4px;background:var(--hm-navbar-hover)}.h-profile{display:inline-flex;align-items:center;gap:6px;height:40px;padding:0 4px 0 2px}.h-profile hm-account-avatar{background:color-mix(in srgb,var(--hm-navbar-text) 18%,transparent);color:var(--hm-navbar-text)}.h-profile-info{display:flex;flex-direction:column;min-width:0;max-width:140px;line-height:1.2;text-align:left}.h-profile-name{font-size:var(--hm-font-size-label);font-weight:var(--hm-font-weight-semibold);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.h-profile-role{color:color-mix(in srgb,var(--hm-navbar-text) 65%,transparent);font-size:var(--hm-font-size-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
2427
2989
  }], ctorParameters: () => [{ type: i1.AuthorizationService }, { type: i2$1.ThemeService }, { type: i2$1.ThemePresetService }, { type: i2$1.DensityService }, { type: i1.ViewService }, { type: i3.Router }, { type: ShortcutService }, { type: undefined, decorators: [{
2428
2990
  type: Optional
2429
2991
  }, {
@@ -4690,13 +5252,13 @@ function buildMcpConfig(servers, token, write, origin) {
4690
5252
  return JSON.stringify({ mcpServers }, null, 2);
4691
5253
  }
4692
5254
  /**
4693
- * Sessões › "Conectar um agente de IA" (`SpineFeatures.aiAgent`): em um passo, cria um token de aplicação pessoal
5255
+ * Sessões › "Conectar um agente de IA" (`SpineFeatures.mcpConnect`): em um passo, cria um token de aplicação pessoal
4694
5256
  * (`x-app-token`, a mesma API de "Tokens de aplicação") e monta a configuração MCP pronta para o Claude Code, com os
4695
5257
  * servidores que o app declarou e os escopos do acesso escolhido (só leitura ou leitura e escrita). O token aparece
4696
5258
  * uma única vez, dentro da configuração; nunca vai para o console nem para o armazenamento do navegador, e some ao
4697
5259
  * concluir ou fechar o painel.
4698
5260
  */
4699
- class AiAgentComponent {
5261
+ class McpConnectComponent {
4700
5262
  constructor() {
4701
5263
  this.messages = inject(SPINE_MESSAGES);
4702
5264
  this.t = this.messages.accountFeatures;
@@ -4706,7 +5268,7 @@ class AiAgentComponent {
4706
5268
  this.toast = inject(HermesToastService);
4707
5269
  this.preferences = inject(SpinePreferencesService);
4708
5270
  this.available = this.service.available;
4709
- this.servers = inject(SPINE_CONFIG, { optional: true })?.features?.aiAgent?.servers ?? [];
5271
+ this.servers = inject(SPINE_CONFIG, { optional: true })?.features?.mcpConnect?.servers ?? [];
4710
5272
  this.open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
4711
5273
  this.starting = signal(false, ...(ngDevMode ? [{ debugName: "starting" }] : /* istanbul ignore next */ []));
4712
5274
  this.submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
@@ -4721,8 +5283,8 @@ class AiAgentComponent {
4721
5283
  this.noBadges = computed(() => !this.badges().length, ...(ngDevMode ? [{ debugName: "noBadges" }] : /* istanbul ignore next */ []));
4722
5284
  this.canSubmit = computed(() => !!this.selected().size && !!this.badgeId() && !this.submitting(), ...(ngDevMode ? [{ debugName: "canSubmit" }] : /* istanbul ignore next */ []));
4723
5285
  this.accessOptions = computed(() => [
4724
- { value: 'read', label: this.t.aiAgentReadOnly },
4725
- { value: 'write', label: this.t.aiAgentReadWrite },
5286
+ { value: 'read', label: this.t.mcpConnectReadOnly },
5287
+ { value: 'write', label: this.t.mcpConnectReadWrite },
4726
5288
  ], ...(ngDevMode ? [{ debugName: "accessOptions" }] : /* istanbul ignore next */ []));
4727
5289
  this.badgeOptions = computed(() => this.badges().map((b) => ({ value: b.id, label: b.name })), ...(ngDevMode ? [{ debugName: "badgeOptions" }] : /* istanbul ignore next */ []));
4728
5290
  this.validityOptions = computed(() => [
@@ -4734,7 +5296,7 @@ class AiAgentComponent {
4734
5296
  }
4735
5297
  serverLabel(server) {
4736
5298
  const label = server.label ?? server.name;
4737
- return server.writeScopes ? label : formatMessage(this.t.aiAgentReadOnlyServer, { name: label });
5299
+ return server.writeScopes ? label : formatMessage(this.t.mcpConnectReadOnlyServer, { name: label });
4738
5300
  }
4739
5301
  toggleServer(name, checked) {
4740
5302
  const next = new Set(this.selected());
@@ -4772,7 +5334,7 @@ class AiAgentComponent {
4772
5334
  const date = new Intl.DateTimeFormat(this.messages.meta.locale, { dateStyle: 'short', timeZone: this.preferences.effectiveTimeZone() }).format(new Date());
4773
5335
  const created = await this.service.create({
4774
5336
  badge_id: this.badgeId(),
4775
- name: formatMessage(this.t.aiAgentTokenName, { date }).slice(0, 80),
5337
+ name: formatMessage(this.t.mcpConnectTokenName, { date }).slice(0, 80),
4776
5338
  validity_days: Number(this.validity()),
4777
5339
  });
4778
5340
  const servers = this.servers.filter((s) => this.selected().has(s.name));
@@ -4785,7 +5347,7 @@ class AiAgentComponent {
4785
5347
  this.open.set(false);
4786
5348
  }
4787
5349
  catch (err) {
4788
- this.toast.error(errorDetail(err) || this.t.aiAgentCreateError);
5350
+ this.toast.error(errorDetail(err) || this.t.mcpConnectCreateError);
4789
5351
  }
4790
5352
  finally {
4791
5353
  this.submitting.set(false);
@@ -4797,7 +5359,7 @@ class AiAgentComponent {
4797
5359
  return;
4798
5360
  try {
4799
5361
  await navigator.clipboard.writeText(config);
4800
- this.toast.success(this.t.aiAgentCopied);
5362
+ this.toast.success(this.t.mcpConnectCopied);
4801
5363
  }
4802
5364
  catch {
4803
5365
  this.toast.error(this.tokens.copyFailed);
@@ -4806,23 +5368,23 @@ class AiAgentComponent {
4806
5368
  done() {
4807
5369
  this.result.set(null);
4808
5370
  }
4809
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AiAgentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4810
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: AiAgentComponent, isStandalone: true, selector: "spine-ai-agent", ngImport: i0, template: `
5371
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: McpConnectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5372
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: McpConnectComponent, isStandalone: true, selector: "spine-mcp-connect", ngImport: i0, template: `
4811
5373
  @if (!available) {
4812
- <p class="sp-account__note">{{ t.aiAgentUnavailable }}</p>
5374
+ <p class="sp-account__note">{{ t.mcpConnectUnavailable }}</p>
4813
5375
  } @else if (result(); as r) {
4814
5376
  <div class="sp-account__panel sp-account__panel--success" role="status">
4815
5377
  <div class="sp-account__panel-head">
4816
5378
  <hm-icon shape="check-circle" />
4817
5379
  <div class="sp-account__panel-text">
4818
- <p class="sp-account__title">{{ t.aiAgentResult }}</p>
4819
- <p class="sp-account__note">{{ fmt(t.aiAgentResultHint, { date: r.expires }) }}</p>
5380
+ <p class="sp-account__title">{{ t.mcpConnectResult }}</p>
5381
+ <p class="sp-account__note">{{ fmt(t.mcpConnectResultHint, { date: r.expires }) }}</p>
4820
5382
  </div>
4821
5383
  </div>
4822
- <hm-code-viewer [code]="r.config" language="json" filename=".mcp.json" [label]="t.aiAgentResult" />
5384
+ <hm-code-viewer [code]="r.config" language="json" filename=".mcp.json" [label]="t.mcpConnectResult" />
4823
5385
  <div class="sp-account__actions">
4824
- <hm-button size="small" emphasis="high" icon="copy" (clicked)="copy()">{{ t.aiAgentCopy }}</hm-button>
4825
- <hm-button size="small" emphasis="medium" (clicked)="done()">{{ t.aiAgentDone }}</hm-button>
5386
+ <hm-button size="small" emphasis="high" icon="copy" (clicked)="copy()">{{ t.mcpConnectCopy }}</hm-button>
5387
+ <hm-button size="small" emphasis="medium" (clicked)="done()">{{ t.mcpConnectDone }}</hm-button>
4826
5388
  </div>
4827
5389
  </div>
4828
5390
  } @else if (open()) {
@@ -4830,7 +5392,7 @@ class AiAgentComponent {
4830
5392
  <div class="sp-account__form">
4831
5393
  @if (servers.length > 1) {
4832
5394
  <fieldset class="sp-account__choices">
4833
- <legend class="sp-account__label">{{ t.aiAgentServers }}</legend>
5395
+ <legend class="sp-account__label">{{ t.mcpConnectServers }}</legend>
4834
5396
  @for (s of servers; track s.name) {
4835
5397
  <hm-checkbox [label]="serverLabel(s)" [checked]="selected().has(s.name)" (checkedChange)="toggleServer(s.name, $event)" />
4836
5398
  }
@@ -4838,9 +5400,9 @@ class AiAgentComponent {
4838
5400
  }
4839
5401
  @if (canWrite()) {
4840
5402
  <div class="sp-account__field">
4841
- <span class="sp-account__label">{{ t.aiAgentAccess }}</span>
4842
- <hm-segmented-control size="small" emphasis="action" [label]="t.aiAgentAccess" [options]="accessOptions()" [value]="access()" (valueChange)="access.set($event === 'write' ? 'write' : 'read')" />
4843
- @if (access() === 'write') { <p class="sp-account__note">{{ t.aiAgentWriteHint }}</p> }
5403
+ <span class="sp-account__label">{{ t.mcpConnectAccess }}</span>
5404
+ <hm-segmented-control size="small" emphasis="action" [label]="t.mcpConnectAccess" [options]="accessOptions()" [value]="access()" (valueChange)="access.set($event === 'write' ? 'write' : 'read')" />
5405
+ @if (access() === 'write') { <p class="sp-account__note">{{ t.mcpConnectWriteHint }}</p> }
4844
5406
  </div>
4845
5407
  }
4846
5408
  @if (badgeOptions().length > 1) {
@@ -4853,39 +5415,39 @@ class AiAgentComponent {
4853
5415
  <span class="sp-account__label">{{ tokens.validity }}</span>
4854
5416
  <hm-segmented-control size="small" emphasis="action" [label]="tokens.validity" [options]="validityOptions()" [value]="validity()" (valueChange)="validity.set($event)" />
4855
5417
  </div>
4856
- @if (!selected().size) { <p class="sp-account__note sp-account__note--error">{{ t.aiAgentSelectServer }}</p> }
5418
+ @if (!selected().size) { <p class="sp-account__note sp-account__note--error">{{ t.mcpConnectSelectServer }}</p> }
4857
5419
  @if (noBadges()) { <p class="sp-account__note sp-account__note--error">{{ tokens.noBadges }}</p> }
4858
5420
  </div>
4859
5421
  <div class="sp-account__actions">
4860
5422
  <hm-button size="small" emphasis="medium" [disabled]="submitting()" (clicked)="cancel()">{{ tokens.cancel }}</hm-button>
4861
- <hm-button size="small" emphasis="high" icon="plugin" [loading]="submitting()" [disabled]="!canSubmit()" (clicked)="generate()">{{ t.aiAgentGenerate }}</hm-button>
5423
+ <hm-button size="small" emphasis="high" icon="plugin" [loading]="submitting()" [disabled]="!canSubmit()" (clicked)="generate()">{{ t.mcpConnectGenerate }}</hm-button>
4862
5424
  </div>
4863
5425
  </div>
4864
5426
  } @else {
4865
5427
  <div class="sp-account__actions">
4866
- <hm-button emphasis="medium" icon="plugin" [loading]="starting()" (clicked)="start()">{{ t.aiAgentStart }}</hm-button>
5428
+ <hm-button emphasis="medium" icon="plugin" [loading]="starting()" (clicked)="start()">{{ t.mcpConnectStart }}</hm-button>
4867
5429
  </div>
4868
5430
  }
4869
5431
  `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CheckboxComponent, selector: "hm-checkbox", inputs: ["label", "labelHidden", "checked", "indeterminate"], outputs: ["checkedChange"] }, { kind: "component", type: CodeViewerComponent, selector: "hm-code-viewer", inputs: ["code", "language", "filename", "label", "showLineNumbers", "firstLineNumber", "highlightLines", "wrap", "tabSize", "copyable", "header", "virtualize", "virtualizeThreshold", "revealInvisibles"], outputs: ["copied", "tokenized"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4870
5432
  }
4871
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: AiAgentComponent, decorators: [{
5433
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: McpConnectComponent, decorators: [{
4872
5434
  type: Component,
4873
- args: [{ selector: 'spine-ai-agent', imports: [ButtonComponent, CheckboxComponent, CodeViewerComponent, IconComponent, SegmentedControlComponent], template: `
5435
+ args: [{ selector: 'spine-mcp-connect', imports: [ButtonComponent, CheckboxComponent, CodeViewerComponent, IconComponent, SegmentedControlComponent], template: `
4874
5436
  @if (!available) {
4875
- <p class="sp-account__note">{{ t.aiAgentUnavailable }}</p>
5437
+ <p class="sp-account__note">{{ t.mcpConnectUnavailable }}</p>
4876
5438
  } @else if (result(); as r) {
4877
5439
  <div class="sp-account__panel sp-account__panel--success" role="status">
4878
5440
  <div class="sp-account__panel-head">
4879
5441
  <hm-icon shape="check-circle" />
4880
5442
  <div class="sp-account__panel-text">
4881
- <p class="sp-account__title">{{ t.aiAgentResult }}</p>
4882
- <p class="sp-account__note">{{ fmt(t.aiAgentResultHint, { date: r.expires }) }}</p>
5443
+ <p class="sp-account__title">{{ t.mcpConnectResult }}</p>
5444
+ <p class="sp-account__note">{{ fmt(t.mcpConnectResultHint, { date: r.expires }) }}</p>
4883
5445
  </div>
4884
5446
  </div>
4885
- <hm-code-viewer [code]="r.config" language="json" filename=".mcp.json" [label]="t.aiAgentResult" />
5447
+ <hm-code-viewer [code]="r.config" language="json" filename=".mcp.json" [label]="t.mcpConnectResult" />
4886
5448
  <div class="sp-account__actions">
4887
- <hm-button size="small" emphasis="high" icon="copy" (clicked)="copy()">{{ t.aiAgentCopy }}</hm-button>
4888
- <hm-button size="small" emphasis="medium" (clicked)="done()">{{ t.aiAgentDone }}</hm-button>
5449
+ <hm-button size="small" emphasis="high" icon="copy" (clicked)="copy()">{{ t.mcpConnectCopy }}</hm-button>
5450
+ <hm-button size="small" emphasis="medium" (clicked)="done()">{{ t.mcpConnectDone }}</hm-button>
4889
5451
  </div>
4890
5452
  </div>
4891
5453
  } @else if (open()) {
@@ -4893,7 +5455,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
4893
5455
  <div class="sp-account__form">
4894
5456
  @if (servers.length > 1) {
4895
5457
  <fieldset class="sp-account__choices">
4896
- <legend class="sp-account__label">{{ t.aiAgentServers }}</legend>
5458
+ <legend class="sp-account__label">{{ t.mcpConnectServers }}</legend>
4897
5459
  @for (s of servers; track s.name) {
4898
5460
  <hm-checkbox [label]="serverLabel(s)" [checked]="selected().has(s.name)" (checkedChange)="toggleServer(s.name, $event)" />
4899
5461
  }
@@ -4901,9 +5463,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
4901
5463
  }
4902
5464
  @if (canWrite()) {
4903
5465
  <div class="sp-account__field">
4904
- <span class="sp-account__label">{{ t.aiAgentAccess }}</span>
4905
- <hm-segmented-control size="small" emphasis="action" [label]="t.aiAgentAccess" [options]="accessOptions()" [value]="access()" (valueChange)="access.set($event === 'write' ? 'write' : 'read')" />
4906
- @if (access() === 'write') { <p class="sp-account__note">{{ t.aiAgentWriteHint }}</p> }
5466
+ <span class="sp-account__label">{{ t.mcpConnectAccess }}</span>
5467
+ <hm-segmented-control size="small" emphasis="action" [label]="t.mcpConnectAccess" [options]="accessOptions()" [value]="access()" (valueChange)="access.set($event === 'write' ? 'write' : 'read')" />
5468
+ @if (access() === 'write') { <p class="sp-account__note">{{ t.mcpConnectWriteHint }}</p> }
4907
5469
  </div>
4908
5470
  }
4909
5471
  @if (badgeOptions().length > 1) {
@@ -4916,17 +5478,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
4916
5478
  <span class="sp-account__label">{{ tokens.validity }}</span>
4917
5479
  <hm-segmented-control size="small" emphasis="action" [label]="tokens.validity" [options]="validityOptions()" [value]="validity()" (valueChange)="validity.set($event)" />
4918
5480
  </div>
4919
- @if (!selected().size) { <p class="sp-account__note sp-account__note--error">{{ t.aiAgentSelectServer }}</p> }
5481
+ @if (!selected().size) { <p class="sp-account__note sp-account__note--error">{{ t.mcpConnectSelectServer }}</p> }
4920
5482
  @if (noBadges()) { <p class="sp-account__note sp-account__note--error">{{ tokens.noBadges }}</p> }
4921
5483
  </div>
4922
5484
  <div class="sp-account__actions">
4923
5485
  <hm-button size="small" emphasis="medium" [disabled]="submitting()" (clicked)="cancel()">{{ tokens.cancel }}</hm-button>
4924
- <hm-button size="small" emphasis="high" icon="plugin" [loading]="submitting()" [disabled]="!canSubmit()" (clicked)="generate()">{{ t.aiAgentGenerate }}</hm-button>
5486
+ <hm-button size="small" emphasis="high" icon="plugin" [loading]="submitting()" [disabled]="!canSubmit()" (clicked)="generate()">{{ t.mcpConnectGenerate }}</hm-button>
4925
5487
  </div>
4926
5488
  </div>
4927
5489
  } @else {
4928
5490
  <div class="sp-account__actions">
4929
- <hm-button emphasis="medium" icon="plugin" [loading]="starting()" (clicked)="start()">{{ t.aiAgentStart }}</hm-button>
5491
+ <hm-button emphasis="medium" icon="plugin" [loading]="starting()" (clicked)="start()">{{ t.mcpConnectStart }}</hm-button>
4930
5492
  </div>
4931
5493
  }
4932
5494
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--hm-space-xs);width:100%;min-width:0}.sp-account__note{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-xs);line-height:1.45}.sp-account__note--error{color:var(--hm-danger)}.sp-account__note code{font-family:var(--hm-font-mono)}.sp-account__list{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-account__items{display:flex;flex-direction:column;min-width:0;margin:0;padding:0;list-style:none}.sp-account__item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-sm)}.sp-account__item+.sp-account__item{border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-account__item--empty{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-account__item--muted .sp-account__item-title strong{color:var(--hm-muted)}.sp-account__icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:var(--hm-control-height-large);height:var(--hm-control-height-large);border-radius:var(--hm-radius-control);background:var(--hm-hover);color:var(--hm-muted)}.sp-account__icon--accent{background:var(--hm-accent-subtle);color:var(--hm-accent-strong)}.sp-account__item-main{display:flex;min-width:0;flex:1 1 14rem;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);font-size:var(--hm-font-size-sm);overflow-wrap:anywhere}.sp-account__item-title{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-nano)}.sp-account__item-title strong{font-weight:var(--hm-font-weight-semibold)}.sp-account__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-account__meta code{font-family:var(--hm-font-mono)}.sp-account__meta--danger{color:var(--hm-danger)}.sp-account__item-actions,.sp-account__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__item-actions{margin-inline-start:auto}.sp-account__list-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--hm-space-xs) var(--hm-space-sm);padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);background:var(--hm-surface-raised)}.sp-account__list-footer:first-child{border-top:0}.sp-account__panel{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-width:0;padding:var(--hm-space-sm) var(--hm-space-md) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-inline-start:var(--hm-border-thin) solid var(--hm-accent);border-radius:var(--hm-radius);background:var(--hm-surface-raised)}.sp-account__panel--success{border-color:var(--hm-success);background:var(--hm-success-bg)}.sp-account__panel--success .sp-account__panel-head hm-icon{color:var(--hm-success)}.sp-account__panel--danger{border-inline-start-color:var(--hm-danger)}.sp-account__panel--danger .sp-account__panel-head hm-icon{color:var(--hm-danger)}.sp-account__panel-head{display:flex;align-items:flex-start;gap:var(--hm-space-xs)}.sp-account__panel-head hm-icon{flex:none;margin-block-start:var(--hm-space-micro);color:var(--hm-accent-strong)}.sp-account__panel-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro)}.sp-account__title{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__form,.sp-account__code,.sp-account__field{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0}.sp-account__form{gap:var(--hm-space-sm)}.sp-account__label{color:var(--hm-text);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.sp-account__qr{align-self:flex-start;padding:var(--hm-space-xxxs);border-radius:var(--hm-radius-control);background:var(--hm-surface)}.sp-account__qr img{display:block;width:10rem;max-width:100%;height:auto}.sp-account__secret,.sp-account__codes code{font-family:var(--hm-font-mono);overflow-wrap:anywhere}.sp-account__secret{-webkit-user-select:all;user-select:all}.sp-account__secret--block{display:block;padding:var(--hm-space-xs) var(--hm-space-xxxs);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-xs)}.sp-account__codes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(9rem,100%),1fr));gap:var(--hm-space-nano) var(--hm-space-sm);margin:0;padding:var(--hm-space-xxxs) var(--hm-space-xs) var(--hm-space-xxxs) var(--hm-space-md);border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface);color:var(--hm-text);font-size:var(--hm-font-size-sm)}@media(forced-colors:active){.sp-account__panel{border-inline-start-color:Highlight}.sp-account__icon{border:var(--hm-border-hairline) solid CanvasText}}.sp-account__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--hm-space-xs)}.sp-account__choices{display:flex;flex-direction:column;gap:var(--hm-space-xs);min-width:0;margin:0;padding:0;border:0}\n"] }]
@@ -6288,7 +6850,7 @@ class SettingsPanelComponent {
6288
6850
  this.sessionsItems = {
6289
6851
  accessToken: this.showAccessToken,
6290
6852
  appTokens: true,
6291
- aiAgent: !!this.config?.features?.aiAgent?.servers?.length,
6853
+ mcpConnect: !!this.config?.features?.mcpConnect?.servers?.length,
6292
6854
  devices: this.accountSecurity,
6293
6855
  };
6294
6856
  /** Sem nenhuma linha visível a seção não aparece. */
@@ -6508,17 +7070,17 @@ class SettingsPanelComponent {
6508
7070
  this.settings.close();
6509
7071
  }
6510
7072
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SettingsPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6511
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SettingsPanelComponent, isStandalone: true, selector: "spine-settings-panel", ngImport: i0, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"activeSection()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- Recursos opcionais (SpineConfig.features): crach\u00E1 padr\u00E3o (Argos), inatividade e tela inicial. -->\n @if (showDefaultBadge) {\n <hm-settings-row [label]=\"featureText.defaultBadge\" [description]=\"featureText.defaultBadgeDescription\" [keywords]=\"featureText.defaultBadgeKeywords\">\n <spine-default-badge />\n </hm-settings-row>\n }\n\n @if (idleOptions().length) {\n <hm-settings-row [label]=\"featureText.idle\" [description]=\"idleHint || featureText.idleDescription\" [keywords]=\"featureText.idleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"featureText.idle\" [options]=\"idleOptions()\" [value]=\"idleValue()\" (valueChange)=\"setIdle($event)\" />\n </hm-settings-row>\n }\n\n @if (features.startPage) {\n <hm-settings-row [label]=\"featureText.startPage\" [description]=\"quickAccessHasFavorites() ? featureText.startPageDescription : featureText.startPageHint\" [keywords]=\"featureText.startPageKeywords\">\n <hm-select class=\"sp-settings__language\" [label]=\"featureText.startPage\" [labelHidden]=\"true\" [value]=\"startPageValue()\" (valueChange)=\"setStartPage($event ?? 'home')\">\n @for (option of startPageOptions(); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [loading]=\"logoutPending()\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 MFA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA e c\u00F3digos de recupera\u00E7\u00E3o aqui mesmo, sem\n abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o, cadastro em\n andamento), que morre quando o painel fecha. `?settings=seguranca` (chave antiga) abre esta se\u00E7\u00E3o. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.mfa\" [label]=\"text.mfa\" icon=\"shield-check\" [description]=\"text.mfaDescription\" [keywords]=\"text.mfaKeywords\">\n <!-- Realm s\u00F3 pelo nome: o id (UUID) n\u00E3o diz nada a quem l\u00EA, ent\u00E3o some. -->\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\" [description]=\"accountRealm ? fmt(text.signedInRealm, { realm: accountRealm }) : ''\">\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <!-- Entradas e tentativas desta conta (SpineFeatures.accountActivity), \u00FAltimos 90 dias. -->\n @if (features.accountActivity) {\n <hm-settings-row [label]=\"featureText.activity\" [description]=\"featureText.activityDescription\" [keywords]=\"featureText.activityKeywords\" [stacked]=\"true\">\n <spine-account-activity />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sess\u00F5es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tokens e onde a conta est\u00E1 conectada (a validade desta sess\u00E3o fica em Conta). Cada linha tem a\n pr\u00F3pria condi\u00E7\u00E3o; a se\u00E7\u00E3o some se nenhuma sobrar (hasSessionsSection). -->\n @if (hasSessionsSection) {\n <hm-settings-section [key]=\"keys.sessions\" [label]=\"text.sessionsSection\" icon=\"devices\" [description]=\"text.sessionsSectionDescription\" [keywords]=\"text.sessionsSectionKeywords\">\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (sessionsItems.accessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n @if (sessionsItems.appTokens) {\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.appTokens })\" [text]=\"text.appTokensHelp\" />\n <spine-app-tokens />\n </hm-settings-row>\n }\n\n <!-- Token + .mcp.json prontos para um agente de IA (SpineFeatures.aiAgent). -->\n @if (sessionsItems.aiAgent) {\n <hm-settings-row [label]=\"featureText.aiAgent\" [description]=\"featureText.aiAgentDescription\" [keywords]=\"featureText.aiAgentKeywords\" [stacked]=\"true\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.aiAgent })\" [text]=\"featureText.aiAgentHelp\" />\n <spine-ai-agent />\n </hm-settings-row>\n }\n\n <!-- Sess\u00F5es e dispositivos: API da conta do Argos, mesma condi\u00E7\u00E3o da se\u00E7\u00E3o MFA. -->\n @if (sessionsItems.devices) {\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Grupos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os grupos desta aplica\u00E7\u00E3o de que a pessoa \u00E9 membro (argos-admin /me/groups, relidos a cada abertura): a lista,\n e escolhendo um, a tela de membros dele. Sem grupo nenhum, a se\u00E7\u00E3o n\u00E3o aparece. -->\n @if (myGroups().length) {\n <hm-settings-section [key]=\"keys.groups\" [label]=\"text.groups\" icon=\"employee-group\" [keywords]=\"text.groupsKeywords\"\n [class.sp-settings__section--group-open]=\"groupOpen()\" [description]=\"groupOpen() ? '' : text.groupsDescription\">\n <spine-my-groups [groups]=\"myGroups()\" (openChange)=\"groupOpen.set($event)\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n class=\"sp-settings__picker\"\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.sidebar })\" [text]=\"text.sidebarHelp\" />\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n\n <!-- Dividir abas: com ela desligada (o padr\u00E3o) a \u00E1rea de abas fica num painel s\u00F3 (SpineWorkspaceNavService). -->\n <hm-settings-row [label]=\"text.splitTabs\" [description]=\"text.splitTabsDescription\" [keywords]=\"text.splitTabsKeywords\">\n <hm-checkbox [label]=\"text.splitTabs\" [labelHidden]=\"true\" [checked]=\"preferences.splitTabs()\" (checkedChange)=\"preferences.setSplitTabs($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Acessibilidade \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Tamanho do texto (--hm-text-scale no <html>, HermesZoomService) e movimento (data-motion no <html>,\n HermesMotionService), guardados pelo SpinePreferencesService. -->\n <hm-settings-section [key]=\"keys.accessibility\" [label]=\"text.accessibility\" icon=\"accessibility-1\" [keywords]=\"text.accessibilityKeywords\">\n <hm-settings-row [label]=\"text.textSize\" [description]=\"text.textSizeDescription\" [keywords]=\"text.textSizeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.textSize\" [options]=\"textSizeOptions()\" [value]=\"textSizeValue()\" (valueChange)=\"setTextSize($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.motion\" [description]=\"text.motionDescription\" [keywords]=\"text.motionKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.motion\" [options]=\"motionOptions()\" [value]=\"preferences.motion()\" (valueChange)=\"setMotion($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [description]=\"fmt(text.timeZoneHint, { now: timeZonePreview() })\" [keywords]=\"text.timeZoneKeywords\">\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.hourCycle\" [description]=\"fmt(text.hourCycleHint, { now: hourCyclePreview() })\" [keywords]=\"text.hourCycleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.hourCycle\" [options]=\"hourCycleOptions()\" [value]=\"preferences.hourCycle()\" (valueChange)=\"setHourCycle($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.weekStart\" [description]=\"text.weekStartDescription\" [keywords]=\"text.weekStartKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.weekStart\" [options]=\"weekStartOptions()\" [value]=\"weekStartValue()\" (valueChange)=\"setWeekStart($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Notifica\u00E7\u00F5es (SpineFeatures.notices) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tipos de aviso que o app declarou (SpineNoticesService) e o aviso do navegador com a aba em segundo plano. -->\n @if (notices.enabled) {\n <hm-settings-section [key]=\"keys.notices\" [label]=\"featureText.notices\" icon=\"bell\" [description]=\"featureText.noticesDescription\" [keywords]=\"featureText.noticesKeywords\">\n @for (type of notices.types; track type.id) {\n <hm-settings-row [label]=\"type.label\" [description]=\"type.description ?? ''\">\n <hm-checkbox [label]=\"type.label\" [labelHidden]=\"true\" [checked]=\"notices.isEnabled(type.id)\" (checkedChange)=\"notices.setEnabled(type.id, $event)\" />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"featureText.browserNotices\" [description]=\"notices.permission() === 'denied' ? featureText.browserNoticesDenied : notices.permission() === 'unsupported' ? featureText.browserNoticesUnsupported : featureText.browserNoticesDescription\">\n <hm-checkbox [label]=\"featureText.browserNotices\" [labelHidden]=\"true\" [checked]=\"notices.browserActive()\" (checkedChange)=\"setBrowserNotices($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list class=\"sp-settings__shortcuts\" characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n </hm-settings-section>\n\n <!-- \u2500\u2500 Dados do navegador \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Listas por usu\u00E1rio do SpineUserStorage e as prefer\u00EAncias deste painel. Destrutivo: o 1\u00BA clique pede\n confirma\u00E7\u00E3o (bot\u00E3o vira \"Confirmar\" + Cancelar), como nas listas de tokens e sess\u00F5es. -->\n <hm-settings-section [key]=\"keys.data\" [label]=\"text.browserData\" icon=\"browser-setting\" [description]=\"text.browserDataDescription\" [keywords]=\"text.browserDataKeywords\">\n <!-- SpineFeatures.accountPreferences: as prefer\u00EAncias tamb\u00E9m na conta do Argos (as telas acima n\u00E3o mudam). -->\n @if (features.accountPreferences) {\n <hm-settings-row [label]=\"featureText.sync\" [description]=\"syncStatus() || featureText.syncDescription\" [keywords]=\"featureText.syncKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.sync })\" [text]=\"featureText.syncDescription\" />\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"accountPreferences.status() === 'local'\" [loading]=\"accountPreferences.status() === 'saving' || accountPreferences.status() === 'loading'\" (clicked)=\"syncNow()\">{{ featureText.syncNow }}</hm-button>\n </hm-settings-row>\n }\n @for (list of browserLists(); track list.id) {\n <hm-settings-row [label]=\"list.label\" [description]=\"list.count\" [keywords]=\"list.keywords\">\n <span class=\"sp-settings__actions\">\n @if (confirming() === list.id) {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button\n [emphasis]=\"confirming() === list.id ? 'high' : 'medium'\"\n intent=\"danger\"\n [disabled]=\"list.empty && confirming() !== list.id\"\n (clicked)=\"clearList(list.id)\"\n >{{ confirming() === list.id ? text.confirmClear : text.clear }}</hm-button>\n </span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.restorePreferences\" [description]=\"text.restorePreferencesDescription\" [keywords]=\"text.restorePreferencesKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.restorePreferences })\" [text]=\"text.restorePreferencesHelp\" />\n <span class=\"sp-settings__actions\">\n @if (confirming() === 'preferences') {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button [emphasis]=\"confirming() === 'preferences' ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"restorePreferences()\">\n {{ confirming() === 'preferences' ? text.confirmRestore : text.restore }}\n </hm-button>\n </span>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Ajuda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Novidades (s\u00F3 com alguma vigente), configura\u00E7\u00E3o inicial e o essencial: fecham o painel antes de abrir. -->\n <hm-settings-section [key]=\"keys.help\" [label]=\"text.help\" icon=\"help\" [keywords]=\"text.helpKeywords\">\n @if (hasNews()) {\n <hm-settings-row [label]=\"text.news\" [description]=\"text.newsDescription\" [keywords]=\"text.newsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"star\" (clicked)=\"showNews()\">{{ text.showNews }}</hm-button>\n </hm-settings-row>\n }\n @if (setupEnabled) {\n <hm-settings-row [label]=\"text.setup\" [description]=\"text.setupDescription\" [keywords]=\"text.setupKeywords\">\n <hm-button emphasis=\"medium\" icon=\"color-palette\" (clicked)=\"redoSetup()\">{{ text.redoSetup }}</hm-button>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.essentials\" [description]=\"text.essentialsDescription\" [keywords]=\"text.essentialsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"help\" (clicked)=\"showEssentials()\">{{ text.showEssentials }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <!-- M\u00E9dia da ferramenta (SpineFeatures.ratings) e \"Avaliar\", que abre o di\u00E1logo da estrela do cabe\u00E7alho. -->\n @if (ratingsEnabled) {\n <hm-settings-row [label]=\"ratingText.aboutRating\" [keywords]=\"ratingText.aboutRatingKeywords\">\n <spine-app-rating-summary />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"medium\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"medium\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"medium\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);overflow-wrap:anywhere}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end;overflow-wrap:anywhere}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:var(--hm-font-mono)}.sp-settings__shortcuts{display:block;padding-block:var(--hm-space-xxxs);border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__links,.sp-settings__actions{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.sp-settings__section--group-open ::ng-deep .hm-settings-section__heading{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__picker ::ng-deep .hm-theme-picker__title{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__deps{display:grid;flex:1;min-width:0;grid-template-columns:repeat(auto-fill,minmax(min(12rem,100%),1fr));margin:0;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xs);margin-block-start:calc(-1 * var(--hm-border-hairline));margin-inline-start:calc(-1 * var(--hm-border-hairline));padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);border-inline-start:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: AppTokensComponent, selector: "spine-app-tokens" }, { kind: "component", type: MyGroupsComponent, selector: "spine-my-groups", inputs: ["groups"], outputs: ["openChange"] }, { kind: "component", type: AiAgentComponent, selector: "spine-ai-agent" }, { kind: "component", type: AccountActivityComponent, selector: "spine-account-activity" }, { kind: "component", type: DefaultBadgeComponent, selector: "spine-default-badge" }, { kind: "component", type: CheckboxComponent, selector: "hm-checkbox", inputs: ["label", "labelHidden", "checked", "indeterminate"], outputs: ["checkedChange"] }, { kind: "component", type: AccountMfaComponent, selector: "spine-account-mfa" }, { kind: "component", type: AccountRecoveryCodesComponent, selector: "spine-account-recovery-codes" }, { kind: "component", type: AccountSessionsComponent, selector: "spine-account-sessions" }, { kind: "directive", type: SpineAccountSecurityScopeDirective, selector: "[spineAccountSecurity]" }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading", "presence", "texts"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SettingsDialogComponent, selector: "hm-settings-dialog", inputs: ["open", "heading", "activeSection", "closeHandler", "actions", "submitLabel", "cancelLabel"], outputs: ["openChange", "activeSectionChange", "closed", "submitted"] }, { kind: "component", type: SettingsRowComponent, selector: "hm-settings-row", inputs: ["label", "description", "keywords", "stacked"] }, { kind: "component", type: SettingsSectionComponent, selector: "hm-settings-section", inputs: ["key", "label", "icon", "description", "keywords", "control", "invalid"] }, { kind: "component", type: SelectComponent, selector: "hm-select", inputs: ["value", "label", "labelHidden", "hint"], outputs: ["valueChange"] }, { kind: "component", type: ShortcutListComponent, selector: "hm-shortcut-list", inputs: ["items", "registered", "searchable", "characterKeysToggle", "headingLevel"] }, { kind: "component", type: ThemePickerComponent, selector: "hm-theme-picker", inputs: ["families", "family", "theme", "showMode"], outputs: ["familyChosen", "themeChosen"] }, { kind: "component", type: ToggletipComponent, selector: "hm-toggletip", inputs: ["label", "text", "side", "intent", "icon"] }, { kind: "component", type: SpineAppRatingSummaryComponent, selector: "spine-app-rating-summary" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7073
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SettingsPanelComponent, isStandalone: true, selector: "spine-settings-panel", ngImport: i0, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"activeSection()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- Recursos opcionais (SpineConfig.features): crach\u00E1 padr\u00E3o (Argos), inatividade e tela inicial. -->\n @if (showDefaultBadge) {\n <hm-settings-row [label]=\"featureText.defaultBadge\" [description]=\"featureText.defaultBadgeDescription\" [keywords]=\"featureText.defaultBadgeKeywords\">\n <spine-default-badge />\n </hm-settings-row>\n }\n\n @if (idleOptions().length) {\n <hm-settings-row [label]=\"featureText.idle\" [description]=\"idleHint || featureText.idleDescription\" [keywords]=\"featureText.idleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"featureText.idle\" [options]=\"idleOptions()\" [value]=\"idleValue()\" (valueChange)=\"setIdle($event)\" />\n </hm-settings-row>\n }\n\n @if (features.startPage) {\n <hm-settings-row [label]=\"featureText.startPage\" [description]=\"quickAccessHasFavorites() ? featureText.startPageDescription : featureText.startPageHint\" [keywords]=\"featureText.startPageKeywords\">\n <hm-select class=\"sp-settings__language\" [label]=\"featureText.startPage\" [labelHidden]=\"true\" [value]=\"startPageValue()\" (valueChange)=\"setStartPage($event ?? 'home')\">\n @for (option of startPageOptions(); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [loading]=\"logoutPending()\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 MFA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA e c\u00F3digos de recupera\u00E7\u00E3o aqui mesmo, sem\n abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o, cadastro em\n andamento), que morre quando o painel fecha. `?settings=seguranca` (chave antiga) abre esta se\u00E7\u00E3o. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.mfa\" [label]=\"text.mfa\" icon=\"shield-check\" [description]=\"text.mfaDescription\" [keywords]=\"text.mfaKeywords\">\n <!-- Realm s\u00F3 pelo nome: o id (UUID) n\u00E3o diz nada a quem l\u00EA, ent\u00E3o some. -->\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\" [description]=\"accountRealm ? fmt(text.signedInRealm, { realm: accountRealm }) : ''\">\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <!-- Entradas e tentativas desta conta (SpineFeatures.accountActivity), \u00FAltimos 90 dias. -->\n @if (features.accountActivity) {\n <hm-settings-row [label]=\"featureText.activity\" [description]=\"featureText.activityDescription\" [keywords]=\"featureText.activityKeywords\" [stacked]=\"true\">\n <spine-account-activity />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sess\u00F5es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tokens e onde a conta est\u00E1 conectada (a validade desta sess\u00E3o fica em Conta). Cada linha tem a\n pr\u00F3pria condi\u00E7\u00E3o; a se\u00E7\u00E3o some se nenhuma sobrar (hasSessionsSection). -->\n @if (hasSessionsSection) {\n <hm-settings-section [key]=\"keys.sessions\" [label]=\"text.sessionsSection\" icon=\"devices\" [description]=\"text.sessionsSectionDescription\" [keywords]=\"text.sessionsSectionKeywords\">\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (sessionsItems.accessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n @if (sessionsItems.appTokens) {\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.appTokens })\" [text]=\"text.appTokensHelp\" />\n <spine-app-tokens />\n </hm-settings-row>\n }\n\n <!-- Token + .mcp.json prontos para um agente de IA (SpineFeatures.mcpConnect). -->\n @if (sessionsItems.mcpConnect) {\n <hm-settings-row [label]=\"featureText.mcpConnect\" [description]=\"featureText.mcpConnectDescription\" [keywords]=\"featureText.mcpConnectKeywords\" [stacked]=\"true\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.mcpConnect })\" [text]=\"featureText.mcpConnectHelp\" />\n <spine-mcp-connect />\n </hm-settings-row>\n }\n\n <!-- Sess\u00F5es e dispositivos: API da conta do Argos, mesma condi\u00E7\u00E3o da se\u00E7\u00E3o MFA. -->\n @if (sessionsItems.devices) {\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Grupos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os grupos desta aplica\u00E7\u00E3o de que a pessoa \u00E9 membro (argos-admin /me/groups, relidos a cada abertura): a lista,\n e escolhendo um, a tela de membros dele. Sem grupo nenhum, a se\u00E7\u00E3o n\u00E3o aparece. -->\n @if (myGroups().length) {\n <hm-settings-section [key]=\"keys.groups\" [label]=\"text.groups\" icon=\"employee-group\" [keywords]=\"text.groupsKeywords\"\n [class.sp-settings__section--group-open]=\"groupOpen()\" [description]=\"groupOpen() ? '' : text.groupsDescription\">\n <spine-my-groups [groups]=\"myGroups()\" (openChange)=\"groupOpen.set($event)\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n class=\"sp-settings__picker\"\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.sidebar })\" [text]=\"text.sidebarHelp\" />\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n\n <!-- Dividir abas: com ela desligada (o padr\u00E3o) a \u00E1rea de abas fica num painel s\u00F3 (SpineWorkspaceNavService). -->\n <hm-settings-row [label]=\"text.splitTabs\" [description]=\"text.splitTabsDescription\" [keywords]=\"text.splitTabsKeywords\">\n <hm-checkbox [label]=\"text.splitTabs\" [labelHidden]=\"true\" [checked]=\"preferences.splitTabs()\" (checkedChange)=\"preferences.setSplitTabs($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Acessibilidade \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Tamanho do texto (--hm-text-scale no <html>, HermesZoomService) e movimento (data-motion no <html>,\n HermesMotionService), guardados pelo SpinePreferencesService. -->\n <hm-settings-section [key]=\"keys.accessibility\" [label]=\"text.accessibility\" icon=\"accessibility-1\" [keywords]=\"text.accessibilityKeywords\">\n <hm-settings-row [label]=\"text.textSize\" [description]=\"text.textSizeDescription\" [keywords]=\"text.textSizeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.textSize\" [options]=\"textSizeOptions()\" [value]=\"textSizeValue()\" (valueChange)=\"setTextSize($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.motion\" [description]=\"text.motionDescription\" [keywords]=\"text.motionKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.motion\" [options]=\"motionOptions()\" [value]=\"preferences.motion()\" (valueChange)=\"setMotion($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [description]=\"fmt(text.timeZoneHint, { now: timeZonePreview() })\" [keywords]=\"text.timeZoneKeywords\">\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.hourCycle\" [description]=\"fmt(text.hourCycleHint, { now: hourCyclePreview() })\" [keywords]=\"text.hourCycleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.hourCycle\" [options]=\"hourCycleOptions()\" [value]=\"preferences.hourCycle()\" (valueChange)=\"setHourCycle($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.weekStart\" [description]=\"text.weekStartDescription\" [keywords]=\"text.weekStartKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.weekStart\" [options]=\"weekStartOptions()\" [value]=\"weekStartValue()\" (valueChange)=\"setWeekStart($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Notifica\u00E7\u00F5es (SpineFeatures.notices) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tipos de aviso que o app declarou (SpineNoticesService) e o aviso do navegador com a aba em segundo plano. -->\n @if (notices.enabled) {\n <hm-settings-section [key]=\"keys.notices\" [label]=\"featureText.notices\" icon=\"bell\" [description]=\"featureText.noticesDescription\" [keywords]=\"featureText.noticesKeywords\">\n @for (type of notices.types; track type.id) {\n <hm-settings-row [label]=\"type.label\" [description]=\"type.description ?? ''\">\n <hm-checkbox [label]=\"type.label\" [labelHidden]=\"true\" [checked]=\"notices.isEnabled(type.id)\" (checkedChange)=\"notices.setEnabled(type.id, $event)\" />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"featureText.browserNotices\" [description]=\"notices.permission() === 'denied' ? featureText.browserNoticesDenied : notices.permission() === 'unsupported' ? featureText.browserNoticesUnsupported : featureText.browserNoticesDescription\">\n <hm-checkbox [label]=\"featureText.browserNotices\" [labelHidden]=\"true\" [checked]=\"notices.browserActive()\" (checkedChange)=\"setBrowserNotices($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list class=\"sp-settings__shortcuts\" characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n </hm-settings-section>\n\n <!-- \u2500\u2500 Dados do navegador \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Listas por usu\u00E1rio do SpineUserStorage e as prefer\u00EAncias deste painel. Destrutivo: o 1\u00BA clique pede\n confirma\u00E7\u00E3o (bot\u00E3o vira \"Confirmar\" + Cancelar), como nas listas de tokens e sess\u00F5es. -->\n <hm-settings-section [key]=\"keys.data\" [label]=\"text.browserData\" icon=\"browser-setting\" [description]=\"text.browserDataDescription\" [keywords]=\"text.browserDataKeywords\">\n <!-- SpineFeatures.accountPreferences: as prefer\u00EAncias tamb\u00E9m na conta do Argos (as telas acima n\u00E3o mudam). -->\n @if (features.accountPreferences) {\n <hm-settings-row [label]=\"featureText.sync\" [description]=\"syncStatus() || featureText.syncDescription\" [keywords]=\"featureText.syncKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.sync })\" [text]=\"featureText.syncDescription\" />\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"accountPreferences.status() === 'local'\" [loading]=\"accountPreferences.status() === 'saving' || accountPreferences.status() === 'loading'\" (clicked)=\"syncNow()\">{{ featureText.syncNow }}</hm-button>\n </hm-settings-row>\n }\n @for (list of browserLists(); track list.id) {\n <hm-settings-row [label]=\"list.label\" [description]=\"list.count\" [keywords]=\"list.keywords\">\n <span class=\"sp-settings__actions\">\n @if (confirming() === list.id) {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button\n [emphasis]=\"confirming() === list.id ? 'high' : 'medium'\"\n intent=\"danger\"\n [disabled]=\"list.empty && confirming() !== list.id\"\n (clicked)=\"clearList(list.id)\"\n >{{ confirming() === list.id ? text.confirmClear : text.clear }}</hm-button>\n </span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.restorePreferences\" [description]=\"text.restorePreferencesDescription\" [keywords]=\"text.restorePreferencesKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.restorePreferences })\" [text]=\"text.restorePreferencesHelp\" />\n <span class=\"sp-settings__actions\">\n @if (confirming() === 'preferences') {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button [emphasis]=\"confirming() === 'preferences' ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"restorePreferences()\">\n {{ confirming() === 'preferences' ? text.confirmRestore : text.restore }}\n </hm-button>\n </span>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Ajuda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Novidades (s\u00F3 com alguma vigente), configura\u00E7\u00E3o inicial e o essencial: fecham o painel antes de abrir. -->\n <hm-settings-section [key]=\"keys.help\" [label]=\"text.help\" icon=\"help\" [keywords]=\"text.helpKeywords\">\n @if (hasNews()) {\n <hm-settings-row [label]=\"text.news\" [description]=\"text.newsDescription\" [keywords]=\"text.newsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"star\" (clicked)=\"showNews()\">{{ text.showNews }}</hm-button>\n </hm-settings-row>\n }\n @if (setupEnabled) {\n <hm-settings-row [label]=\"text.setup\" [description]=\"text.setupDescription\" [keywords]=\"text.setupKeywords\">\n <hm-button emphasis=\"medium\" icon=\"color-palette\" (clicked)=\"redoSetup()\">{{ text.redoSetup }}</hm-button>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.essentials\" [description]=\"text.essentialsDescription\" [keywords]=\"text.essentialsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"help\" (clicked)=\"showEssentials()\">{{ text.showEssentials }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <!-- M\u00E9dia da ferramenta (SpineFeatures.ratings) e \"Avaliar\", que abre o di\u00E1logo da estrela do cabe\u00E7alho. -->\n @if (ratingsEnabled) {\n <hm-settings-row [label]=\"ratingText.aboutRating\" [keywords]=\"ratingText.aboutRatingKeywords\">\n <spine-app-rating-summary />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"medium\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"medium\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"medium\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);overflow-wrap:anywhere}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end;overflow-wrap:anywhere}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:var(--hm-font-mono)}.sp-settings__shortcuts{display:block;padding-block:var(--hm-space-xxxs);border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__links,.sp-settings__actions{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.sp-settings__section--group-open ::ng-deep .hm-settings-section__heading{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__picker ::ng-deep .hm-theme-picker__title{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__deps{display:grid;flex:1;min-width:0;grid-template-columns:repeat(auto-fill,minmax(min(12rem,100%),1fr));margin:0;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xs);margin-block-start:calc(-1 * var(--hm-border-hairline));margin-inline-start:calc(-1 * var(--hm-border-hairline));padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);border-inline-start:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: AppTokensComponent, selector: "spine-app-tokens" }, { kind: "component", type: MyGroupsComponent, selector: "spine-my-groups", inputs: ["groups"], outputs: ["openChange"] }, { kind: "component", type: McpConnectComponent, selector: "spine-mcp-connect" }, { kind: "component", type: AccountActivityComponent, selector: "spine-account-activity" }, { kind: "component", type: DefaultBadgeComponent, selector: "spine-default-badge" }, { kind: "component", type: CheckboxComponent, selector: "hm-checkbox", inputs: ["label", "labelHidden", "checked", "indeterminate"], outputs: ["checkedChange"] }, { kind: "component", type: AccountMfaComponent, selector: "spine-account-mfa" }, { kind: "component", type: AccountRecoveryCodesComponent, selector: "spine-account-recovery-codes" }, { kind: "component", type: AccountSessionsComponent, selector: "spine-account-sessions" }, { kind: "directive", type: SpineAccountSecurityScopeDirective, selector: "[spineAccountSecurity]" }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading", "presence", "texts"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SettingsDialogComponent, selector: "hm-settings-dialog", inputs: ["open", "heading", "activeSection", "closeHandler", "actions", "submitLabel", "cancelLabel"], outputs: ["openChange", "activeSectionChange", "closed", "submitted"] }, { kind: "component", type: SettingsRowComponent, selector: "hm-settings-row", inputs: ["label", "description", "keywords", "stacked"] }, { kind: "component", type: SettingsSectionComponent, selector: "hm-settings-section", inputs: ["key", "label", "icon", "description", "keywords", "control", "invalid"] }, { kind: "component", type: SelectComponent, selector: "hm-select", inputs: ["value", "label", "labelHidden", "hint"], outputs: ["valueChange"] }, { kind: "component", type: ShortcutListComponent, selector: "hm-shortcut-list", inputs: ["items", "registered", "searchable", "characterKeysToggle", "headingLevel"] }, { kind: "component", type: ThemePickerComponent, selector: "hm-theme-picker", inputs: ["families", "family", "theme", "showMode"], outputs: ["familyChosen", "themeChosen"] }, { kind: "component", type: ToggletipComponent, selector: "hm-toggletip", inputs: ["label", "text", "side", "intent", "icon"] }, { kind: "component", type: SpineAppRatingSummaryComponent, selector: "spine-app-rating-summary" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6512
7074
  }
6513
7075
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SettingsPanelComponent, decorators: [{
6514
7076
  type: Component,
6515
7077
  args: [{ selector: 'spine-settings-panel', imports: [
6516
- NgComponentOutlet, AppTokensComponent, MyGroupsComponent, AiAgentComponent, AccountActivityComponent, DefaultBadgeComponent, CheckboxComponent,
7078
+ NgComponentOutlet, AppTokensComponent, MyGroupsComponent, McpConnectComponent, AccountActivityComponent, DefaultBadgeComponent, CheckboxComponent,
6517
7079
  AccountMfaComponent, AccountRecoveryCodesComponent, AccountSessionsComponent, SpineAccountSecurityScopeDirective,
6518
7080
  AccountAvatarComponent, ButtonComponent, ComboboxComponent,
6519
7081
  SegmentedControlComponent, SettingsDialogComponent, SettingsRowComponent, SettingsSectionComponent,
6520
7082
  SelectComponent, ShortcutListComponent, ThemePickerComponent, ToggletipComponent, SpineAppRatingSummaryComponent,
6521
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"activeSection()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- Recursos opcionais (SpineConfig.features): crach\u00E1 padr\u00E3o (Argos), inatividade e tela inicial. -->\n @if (showDefaultBadge) {\n <hm-settings-row [label]=\"featureText.defaultBadge\" [description]=\"featureText.defaultBadgeDescription\" [keywords]=\"featureText.defaultBadgeKeywords\">\n <spine-default-badge />\n </hm-settings-row>\n }\n\n @if (idleOptions().length) {\n <hm-settings-row [label]=\"featureText.idle\" [description]=\"idleHint || featureText.idleDescription\" [keywords]=\"featureText.idleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"featureText.idle\" [options]=\"idleOptions()\" [value]=\"idleValue()\" (valueChange)=\"setIdle($event)\" />\n </hm-settings-row>\n }\n\n @if (features.startPage) {\n <hm-settings-row [label]=\"featureText.startPage\" [description]=\"quickAccessHasFavorites() ? featureText.startPageDescription : featureText.startPageHint\" [keywords]=\"featureText.startPageKeywords\">\n <hm-select class=\"sp-settings__language\" [label]=\"featureText.startPage\" [labelHidden]=\"true\" [value]=\"startPageValue()\" (valueChange)=\"setStartPage($event ?? 'home')\">\n @for (option of startPageOptions(); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [loading]=\"logoutPending()\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 MFA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA e c\u00F3digos de recupera\u00E7\u00E3o aqui mesmo, sem\n abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o, cadastro em\n andamento), que morre quando o painel fecha. `?settings=seguranca` (chave antiga) abre esta se\u00E7\u00E3o. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.mfa\" [label]=\"text.mfa\" icon=\"shield-check\" [description]=\"text.mfaDescription\" [keywords]=\"text.mfaKeywords\">\n <!-- Realm s\u00F3 pelo nome: o id (UUID) n\u00E3o diz nada a quem l\u00EA, ent\u00E3o some. -->\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\" [description]=\"accountRealm ? fmt(text.signedInRealm, { realm: accountRealm }) : ''\">\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <!-- Entradas e tentativas desta conta (SpineFeatures.accountActivity), \u00FAltimos 90 dias. -->\n @if (features.accountActivity) {\n <hm-settings-row [label]=\"featureText.activity\" [description]=\"featureText.activityDescription\" [keywords]=\"featureText.activityKeywords\" [stacked]=\"true\">\n <spine-account-activity />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sess\u00F5es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tokens e onde a conta est\u00E1 conectada (a validade desta sess\u00E3o fica em Conta). Cada linha tem a\n pr\u00F3pria condi\u00E7\u00E3o; a se\u00E7\u00E3o some se nenhuma sobrar (hasSessionsSection). -->\n @if (hasSessionsSection) {\n <hm-settings-section [key]=\"keys.sessions\" [label]=\"text.sessionsSection\" icon=\"devices\" [description]=\"text.sessionsSectionDescription\" [keywords]=\"text.sessionsSectionKeywords\">\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (sessionsItems.accessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n @if (sessionsItems.appTokens) {\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.appTokens })\" [text]=\"text.appTokensHelp\" />\n <spine-app-tokens />\n </hm-settings-row>\n }\n\n <!-- Token + .mcp.json prontos para um agente de IA (SpineFeatures.aiAgent). -->\n @if (sessionsItems.aiAgent) {\n <hm-settings-row [label]=\"featureText.aiAgent\" [description]=\"featureText.aiAgentDescription\" [keywords]=\"featureText.aiAgentKeywords\" [stacked]=\"true\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.aiAgent })\" [text]=\"featureText.aiAgentHelp\" />\n <spine-ai-agent />\n </hm-settings-row>\n }\n\n <!-- Sess\u00F5es e dispositivos: API da conta do Argos, mesma condi\u00E7\u00E3o da se\u00E7\u00E3o MFA. -->\n @if (sessionsItems.devices) {\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Grupos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os grupos desta aplica\u00E7\u00E3o de que a pessoa \u00E9 membro (argos-admin /me/groups, relidos a cada abertura): a lista,\n e escolhendo um, a tela de membros dele. Sem grupo nenhum, a se\u00E7\u00E3o n\u00E3o aparece. -->\n @if (myGroups().length) {\n <hm-settings-section [key]=\"keys.groups\" [label]=\"text.groups\" icon=\"employee-group\" [keywords]=\"text.groupsKeywords\"\n [class.sp-settings__section--group-open]=\"groupOpen()\" [description]=\"groupOpen() ? '' : text.groupsDescription\">\n <spine-my-groups [groups]=\"myGroups()\" (openChange)=\"groupOpen.set($event)\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n class=\"sp-settings__picker\"\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.sidebar })\" [text]=\"text.sidebarHelp\" />\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n\n <!-- Dividir abas: com ela desligada (o padr\u00E3o) a \u00E1rea de abas fica num painel s\u00F3 (SpineWorkspaceNavService). -->\n <hm-settings-row [label]=\"text.splitTabs\" [description]=\"text.splitTabsDescription\" [keywords]=\"text.splitTabsKeywords\">\n <hm-checkbox [label]=\"text.splitTabs\" [labelHidden]=\"true\" [checked]=\"preferences.splitTabs()\" (checkedChange)=\"preferences.setSplitTabs($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Acessibilidade \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Tamanho do texto (--hm-text-scale no <html>, HermesZoomService) e movimento (data-motion no <html>,\n HermesMotionService), guardados pelo SpinePreferencesService. -->\n <hm-settings-section [key]=\"keys.accessibility\" [label]=\"text.accessibility\" icon=\"accessibility-1\" [keywords]=\"text.accessibilityKeywords\">\n <hm-settings-row [label]=\"text.textSize\" [description]=\"text.textSizeDescription\" [keywords]=\"text.textSizeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.textSize\" [options]=\"textSizeOptions()\" [value]=\"textSizeValue()\" (valueChange)=\"setTextSize($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.motion\" [description]=\"text.motionDescription\" [keywords]=\"text.motionKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.motion\" [options]=\"motionOptions()\" [value]=\"preferences.motion()\" (valueChange)=\"setMotion($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [description]=\"fmt(text.timeZoneHint, { now: timeZonePreview() })\" [keywords]=\"text.timeZoneKeywords\">\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.hourCycle\" [description]=\"fmt(text.hourCycleHint, { now: hourCyclePreview() })\" [keywords]=\"text.hourCycleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.hourCycle\" [options]=\"hourCycleOptions()\" [value]=\"preferences.hourCycle()\" (valueChange)=\"setHourCycle($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.weekStart\" [description]=\"text.weekStartDescription\" [keywords]=\"text.weekStartKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.weekStart\" [options]=\"weekStartOptions()\" [value]=\"weekStartValue()\" (valueChange)=\"setWeekStart($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Notifica\u00E7\u00F5es (SpineFeatures.notices) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tipos de aviso que o app declarou (SpineNoticesService) e o aviso do navegador com a aba em segundo plano. -->\n @if (notices.enabled) {\n <hm-settings-section [key]=\"keys.notices\" [label]=\"featureText.notices\" icon=\"bell\" [description]=\"featureText.noticesDescription\" [keywords]=\"featureText.noticesKeywords\">\n @for (type of notices.types; track type.id) {\n <hm-settings-row [label]=\"type.label\" [description]=\"type.description ?? ''\">\n <hm-checkbox [label]=\"type.label\" [labelHidden]=\"true\" [checked]=\"notices.isEnabled(type.id)\" (checkedChange)=\"notices.setEnabled(type.id, $event)\" />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"featureText.browserNotices\" [description]=\"notices.permission() === 'denied' ? featureText.browserNoticesDenied : notices.permission() === 'unsupported' ? featureText.browserNoticesUnsupported : featureText.browserNoticesDescription\">\n <hm-checkbox [label]=\"featureText.browserNotices\" [labelHidden]=\"true\" [checked]=\"notices.browserActive()\" (checkedChange)=\"setBrowserNotices($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list class=\"sp-settings__shortcuts\" characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n </hm-settings-section>\n\n <!-- \u2500\u2500 Dados do navegador \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Listas por usu\u00E1rio do SpineUserStorage e as prefer\u00EAncias deste painel. Destrutivo: o 1\u00BA clique pede\n confirma\u00E7\u00E3o (bot\u00E3o vira \"Confirmar\" + Cancelar), como nas listas de tokens e sess\u00F5es. -->\n <hm-settings-section [key]=\"keys.data\" [label]=\"text.browserData\" icon=\"browser-setting\" [description]=\"text.browserDataDescription\" [keywords]=\"text.browserDataKeywords\">\n <!-- SpineFeatures.accountPreferences: as prefer\u00EAncias tamb\u00E9m na conta do Argos (as telas acima n\u00E3o mudam). -->\n @if (features.accountPreferences) {\n <hm-settings-row [label]=\"featureText.sync\" [description]=\"syncStatus() || featureText.syncDescription\" [keywords]=\"featureText.syncKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.sync })\" [text]=\"featureText.syncDescription\" />\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"accountPreferences.status() === 'local'\" [loading]=\"accountPreferences.status() === 'saving' || accountPreferences.status() === 'loading'\" (clicked)=\"syncNow()\">{{ featureText.syncNow }}</hm-button>\n </hm-settings-row>\n }\n @for (list of browserLists(); track list.id) {\n <hm-settings-row [label]=\"list.label\" [description]=\"list.count\" [keywords]=\"list.keywords\">\n <span class=\"sp-settings__actions\">\n @if (confirming() === list.id) {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button\n [emphasis]=\"confirming() === list.id ? 'high' : 'medium'\"\n intent=\"danger\"\n [disabled]=\"list.empty && confirming() !== list.id\"\n (clicked)=\"clearList(list.id)\"\n >{{ confirming() === list.id ? text.confirmClear : text.clear }}</hm-button>\n </span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.restorePreferences\" [description]=\"text.restorePreferencesDescription\" [keywords]=\"text.restorePreferencesKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.restorePreferences })\" [text]=\"text.restorePreferencesHelp\" />\n <span class=\"sp-settings__actions\">\n @if (confirming() === 'preferences') {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button [emphasis]=\"confirming() === 'preferences' ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"restorePreferences()\">\n {{ confirming() === 'preferences' ? text.confirmRestore : text.restore }}\n </hm-button>\n </span>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Ajuda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Novidades (s\u00F3 com alguma vigente), configura\u00E7\u00E3o inicial e o essencial: fecham o painel antes de abrir. -->\n <hm-settings-section [key]=\"keys.help\" [label]=\"text.help\" icon=\"help\" [keywords]=\"text.helpKeywords\">\n @if (hasNews()) {\n <hm-settings-row [label]=\"text.news\" [description]=\"text.newsDescription\" [keywords]=\"text.newsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"star\" (clicked)=\"showNews()\">{{ text.showNews }}</hm-button>\n </hm-settings-row>\n }\n @if (setupEnabled) {\n <hm-settings-row [label]=\"text.setup\" [description]=\"text.setupDescription\" [keywords]=\"text.setupKeywords\">\n <hm-button emphasis=\"medium\" icon=\"color-palette\" (clicked)=\"redoSetup()\">{{ text.redoSetup }}</hm-button>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.essentials\" [description]=\"text.essentialsDescription\" [keywords]=\"text.essentialsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"help\" (clicked)=\"showEssentials()\">{{ text.showEssentials }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <!-- M\u00E9dia da ferramenta (SpineFeatures.ratings) e \"Avaliar\", que abre o di\u00E1logo da estrela do cabe\u00E7alho. -->\n @if (ratingsEnabled) {\n <hm-settings-row [label]=\"ratingText.aboutRating\" [keywords]=\"ratingText.aboutRatingKeywords\">\n <spine-app-rating-summary />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"medium\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"medium\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"medium\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);overflow-wrap:anywhere}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end;overflow-wrap:anywhere}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:var(--hm-font-mono)}.sp-settings__shortcuts{display:block;padding-block:var(--hm-space-xxxs);border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__links,.sp-settings__actions{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.sp-settings__section--group-open ::ng-deep .hm-settings-section__heading{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__picker ::ng-deep .hm-theme-picker__title{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__deps{display:grid;flex:1;min-width:0;grid-template-columns:repeat(auto-fill,minmax(min(12rem,100%),1fr));margin:0;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xs);margin-block-start:calc(-1 * var(--hm-border-hairline));margin-inline-start:calc(-1 * var(--hm-border-hairline));padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);border-inline-start:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"] }]
7083
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Montado s\u00F3 enquanto aberto: nada disto (lista de fusos, leitura do token...) custa no\n carregamento de toda p\u00E1gina. -->\n@if (settings.isOpen()) {\n <hm-settings-dialog\n [heading]=\"text.heading\"\n [open]=\"true\"\n (openChange)=\"onOpenChange($event)\"\n [activeSection]=\"activeSection()\"\n (activeSectionChange)=\"settings.section.set($event)\"\n >\n <!-- \u2500\u2500 Conta \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.account\" [label]=\"text.account\" icon=\"user\" [keywords]=\"text.accountKeywords\">\n <div class=\"sp-settings__profile\">\n <hm-account-avatar [name]=\"profile.name ?? text.user\" [imageUrl]=\"profile.picture\" />\n <span class=\"sp-settings__profile-text\">\n <strong>{{ profile.name ?? text.user }}</strong>\n @if (profile.role) { <span>{{ profile.role }}</span> }\n </span>\n </div>\n\n <!-- S\u00F3 informativo: a troca de n\u00EDvel fica no menu do perfil, perto de onde se trabalha. -->\n @if (badge) {\n <hm-settings-row [label]=\"text.accessLevel\" [keywords]=\"text.accessLevelKeywords\">\n <span class=\"sp-settings__value\">{{ badge.name }}</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.session\" [description]=\"text.sessionDescription\" [keywords]=\"text.sessionKeywords\">\n <span class=\"sp-settings__value\">{{ sessionExpiry }}</span>\n </hm-settings-row>\n\n <!-- Recursos opcionais (SpineConfig.features): crach\u00E1 padr\u00E3o (Argos), inatividade e tela inicial. -->\n @if (showDefaultBadge) {\n <hm-settings-row [label]=\"featureText.defaultBadge\" [description]=\"featureText.defaultBadgeDescription\" [keywords]=\"featureText.defaultBadgeKeywords\">\n <spine-default-badge />\n </hm-settings-row>\n }\n\n @if (idleOptions().length) {\n <hm-settings-row [label]=\"featureText.idle\" [description]=\"idleHint || featureText.idleDescription\" [keywords]=\"featureText.idleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"featureText.idle\" [options]=\"idleOptions()\" [value]=\"idleValue()\" (valueChange)=\"setIdle($event)\" />\n </hm-settings-row>\n }\n\n @if (features.startPage) {\n <hm-settings-row [label]=\"featureText.startPage\" [description]=\"quickAccessHasFavorites() ? featureText.startPageDescription : featureText.startPageHint\" [keywords]=\"featureText.startPageKeywords\">\n <hm-select class=\"sp-settings__language\" [label]=\"featureText.startPage\" [labelHidden]=\"true\" [value]=\"startPageValue()\" (valueChange)=\"setStartPage($event ?? 'home')\">\n @for (option of startPageOptions(); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.logout\" [description]=\"text.logoutDescription\" [keywords]=\"text.logoutKeywords\">\n <hm-button emphasis=\"medium\" intent=\"danger\" icon=\"logout\" [loading]=\"logoutPending()\" (clicked)=\"logout()\">{{ text.logout }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 MFA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- API da conta do Argos com o token da sess\u00E3o (Bearer): MFA e c\u00F3digos de recupera\u00E7\u00E3o aqui mesmo, sem\n abrir o Argos. O spineAccountSecurity d\u00E1 \u00E0s linhas o mesmo estado (reautentica\u00E7\u00E3o, cadastro em\n andamento), que morre quando o painel fecha. `?settings=seguranca` (chave antiga) abre esta se\u00E7\u00E3o. -->\n @if (accountSecurity) {\n <hm-settings-section spineAccountSecurity [key]=\"keys.mfa\" [label]=\"text.mfa\" icon=\"shield-check\" [description]=\"text.mfaDescription\" [keywords]=\"text.mfaKeywords\">\n <!-- Realm s\u00F3 pelo nome: o id (UUID) n\u00E3o diz nada a quem l\u00EA, ent\u00E3o some. -->\n @if (accountLogin || accountRealm) {\n <hm-settings-row [label]=\"text.signedInAccount\" [description]=\"accountRealm ? fmt(text.signedInRealm, { realm: accountRealm }) : ''\">\n <span class=\"sp-settings__value\">{{ profile.name ?? text.user }}@if (accountLogin) { <span class=\"sp-settings__muted\"> \u00B7 {{ accountLogin }}</span> }</span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.twoFactor\" [description]=\"text.twoFactorDescription\" [keywords]=\"text.twoFactorKeywords\" [stacked]=\"true\">\n <spine-account-mfa />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.recoveryCodes\" [description]=\"text.recoveryCodesDescription\" [keywords]=\"text.recoveryCodesKeywords\" [stacked]=\"true\">\n <spine-account-recovery-codes />\n </hm-settings-row>\n\n <!-- Entradas e tentativas desta conta (SpineFeatures.accountActivity), \u00FAltimos 90 dias. -->\n @if (features.accountActivity) {\n <hm-settings-row [label]=\"featureText.activity\" [description]=\"featureText.activityDescription\" [keywords]=\"featureText.activityKeywords\" [stacked]=\"true\">\n <spine-account-activity />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sess\u00F5es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tokens e onde a conta est\u00E1 conectada (a validade desta sess\u00E3o fica em Conta). Cada linha tem a\n pr\u00F3pria condi\u00E7\u00E3o; a se\u00E7\u00E3o some se nenhuma sobrar (hasSessionsSection). -->\n @if (hasSessionsSection) {\n <hm-settings-section [key]=\"keys.sessions\" [label]=\"text.sessionsSection\" icon=\"devices\" [description]=\"text.sessionsSectionDescription\" [keywords]=\"text.sessionsSectionKeywords\">\n <!-- S\u00F3 com SpineConfig.technicalDocs (padr\u00E3o: isDevMode()): token da sess\u00E3o no clipboard \u00E9 ferramenta de dev. -->\n @if (sessionsItems.accessToken) {\n <hm-settings-row [label]=\"text.accessToken\" [description]=\"text.accessTokenDescription\" [keywords]=\"text.accessTokenKeywords\">\n <hm-button emphasis=\"medium\" icon=\"copy\" (clicked)=\"copyToken()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n }\n\n @if (sessionsItems.appTokens) {\n <hm-settings-row [label]=\"text.appTokens\" [stacked]=\"true\"\n [description]=\"text.appTokensDescription\"\n [keywords]=\"text.appTokensKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.appTokens })\" [text]=\"text.appTokensHelp\" />\n <spine-app-tokens />\n </hm-settings-row>\n }\n\n <!-- Token + .mcp.json prontos para um agente de IA (SpineFeatures.mcpConnect). -->\n @if (sessionsItems.mcpConnect) {\n <hm-settings-row [label]=\"featureText.mcpConnect\" [description]=\"featureText.mcpConnectDescription\" [keywords]=\"featureText.mcpConnectKeywords\" [stacked]=\"true\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.mcpConnect })\" [text]=\"featureText.mcpConnectHelp\" />\n <spine-mcp-connect />\n </hm-settings-row>\n }\n\n <!-- Sess\u00F5es e dispositivos: API da conta do Argos, mesma condi\u00E7\u00E3o da se\u00E7\u00E3o MFA. -->\n @if (sessionsItems.devices) {\n <hm-settings-row [label]=\"text.sessions\" [description]=\"text.sessionsDescription\" [keywords]=\"text.sessionsKeywords\" [stacked]=\"true\">\n <spine-account-sessions />\n </hm-settings-row>\n }\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Grupos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os grupos desta aplica\u00E7\u00E3o de que a pessoa \u00E9 membro (argos-admin /me/groups, relidos a cada abertura): a lista,\n e escolhendo um, a tela de membros dele. Sem grupo nenhum, a se\u00E7\u00E3o n\u00E3o aparece. -->\n @if (myGroups().length) {\n <hm-settings-section [key]=\"keys.groups\" [label]=\"text.groups\" icon=\"employee-group\" [keywords]=\"text.groupsKeywords\"\n [class.sp-settings__section--group-open]=\"groupOpen()\" [description]=\"groupOpen() ? '' : text.groupsDescription\">\n <spine-my-groups [groups]=\"myGroups()\" (openChange)=\"groupOpen.set($event)\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Apar\u00EAncia \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.appearance\" [label]=\"text.appearance\" icon=\"color-palette\" [keywords]=\"text.appearanceKeywords\">\n <hm-settings-row [label]=\"text.theme\" [description]=\"text.themeDescription\" [keywords]=\"text.themeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.theme\" [options]=\"themeOptions()\" [value]=\"themeMode()\" (valueChange)=\"setThemeMode($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.accent\" [description]=\"text.accentDescription\" [keywords]=\"text.accentKeywords\" [stacked]=\"true\">\n <hm-theme-picker\n class=\"sp-settings__picker\"\n [families]=\"families\"\n [family]=\"family()\"\n [theme]=\"appliedTheme()\"\n [showMode]=\"false\"\n (familyChosen)=\"setFamily($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.density\" [description]=\"text.densityDescription\" [keywords]=\"text.densityKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.density\" [options]=\"densityOptions()\" [value]=\"densityValue()\" (valueChange)=\"setDensity($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.font\" [description]=\"text.fontDescription\" [keywords]=\"text.fontKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.font\" [options]=\"fontOptions\" [value]=\"fontValue()\" (valueChange)=\"setFont($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.sidebar\" [description]=\"text.sidebarDescription\" [keywords]=\"text.sidebarKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.sidebar })\" [text]=\"text.sidebarHelp\" />\n <hm-segmented-control emphasis=\"action\" [label]=\"text.sidebar\" [options]=\"sidebarOptions()\" [value]=\"sidebar.mode()\" (valueChange)=\"setSidebarMode($event)\" />\n </hm-settings-row>\n\n <!-- Dividir abas: com ela desligada (o padr\u00E3o) a \u00E1rea de abas fica num painel s\u00F3 (SpineWorkspaceNavService). -->\n <hm-settings-row [label]=\"text.splitTabs\" [description]=\"text.splitTabsDescription\" [keywords]=\"text.splitTabsKeywords\">\n <hm-checkbox [label]=\"text.splitTabs\" [labelHidden]=\"true\" [checked]=\"preferences.splitTabs()\" (checkedChange)=\"preferences.setSplitTabs($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Acessibilidade \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Tamanho do texto (--hm-text-scale no <html>, HermesZoomService) e movimento (data-motion no <html>,\n HermesMotionService), guardados pelo SpinePreferencesService. -->\n <hm-settings-section [key]=\"keys.accessibility\" [label]=\"text.accessibility\" icon=\"accessibility-1\" [keywords]=\"text.accessibilityKeywords\">\n <hm-settings-row [label]=\"text.textSize\" [description]=\"text.textSizeDescription\" [keywords]=\"text.textSizeKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.textSize\" [options]=\"textSizeOptions()\" [value]=\"textSizeValue()\" (valueChange)=\"setTextSize($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.motion\" [description]=\"text.motionDescription\" [keywords]=\"text.motionKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.motion\" [options]=\"motionOptions()\" [value]=\"preferences.motion()\" (valueChange)=\"setMotion($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Regi\u00E3o \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.region\" [label]=\"text.region\" icon=\"clock\" [keywords]=\"text.regionKeywords\">\n <hm-settings-row [label]=\"text.language\" [description]=\"text.languageDescription\" [keywords]=\"text.languageKeywords\">\n <hm-select\n class=\"sp-settings__language\"\n [label]=\"text.language\"\n [labelHidden]=\"true\"\n [value]=\"preferences.language()\"\n (valueChange)=\"preferences.setLanguage($event ?? '')\"\n >\n @for (option of languageOptions(); track option.value) {\n <option [value]=\"option.value\" [attr.lang]=\"option.value || null\">{{ option.label }}</option>\n }\n </hm-select>\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.timeZone\" [description]=\"fmt(text.timeZoneHint, { now: timeZonePreview() })\" [keywords]=\"text.timeZoneKeywords\">\n <hm-combobox\n class=\"sp-settings__timezone\"\n [label]=\"text.timeZone\"\n [labelHidden]=\"true\"\n icon=\"world\"\n [options]=\"timeZoneOptions()\"\n [value]=\"preferences.timeZone()\"\n (valueChange)=\"preferences.setTimeZone($event)\"\n />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.hourCycle\" [description]=\"fmt(text.hourCycleHint, { now: hourCyclePreview() })\" [keywords]=\"text.hourCycleKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.hourCycle\" [options]=\"hourCycleOptions()\" [value]=\"preferences.hourCycle()\" (valueChange)=\"setHourCycle($event)\" />\n </hm-settings-row>\n\n <hm-settings-row [label]=\"text.weekStart\" [description]=\"text.weekStartDescription\" [keywords]=\"text.weekStartKeywords\">\n <hm-segmented-control emphasis=\"action\" [label]=\"text.weekStart\" [options]=\"weekStartOptions()\" [value]=\"weekStartValue()\" (valueChange)=\"setWeekStart($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Notifica\u00E7\u00F5es (SpineFeatures.notices) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Os tipos de aviso que o app declarou (SpineNoticesService) e o aviso do navegador com a aba em segundo plano. -->\n @if (notices.enabled) {\n <hm-settings-section [key]=\"keys.notices\" [label]=\"featureText.notices\" icon=\"bell\" [description]=\"featureText.noticesDescription\" [keywords]=\"featureText.noticesKeywords\">\n @for (type of notices.types; track type.id) {\n <hm-settings-row [label]=\"type.label\" [description]=\"type.description ?? ''\">\n <hm-checkbox [label]=\"type.label\" [labelHidden]=\"true\" [checked]=\"notices.isEnabled(type.id)\" (checkedChange)=\"notices.setEnabled(type.id, $event)\" />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"featureText.browserNotices\" [description]=\"notices.permission() === 'denied' ? featureText.browserNoticesDenied : notices.permission() === 'unsupported' ? featureText.browserNoticesUnsupported : featureText.browserNoticesDescription\">\n <hm-checkbox [label]=\"featureText.browserNotices\" [labelHidden]=\"true\" [checked]=\"notices.browserActive()\" (checkedChange)=\"setBrowserNotices($event)\" />\n </hm-settings-row>\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Atalhos \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.shortcuts\" [label]=\"text.shortcuts\" icon=\"keyboard\" [description]=\"text.shortcutsDescription\" [keywords]=\"text.shortcutsKeywords\">\n <!-- Mesma lista do painel \"?\", sem busca (a do di\u00E1logo de configura\u00E7\u00F5es j\u00E1 cobre a se\u00E7\u00E3o);\n o interruptor de tecla \u00FAnica (WCAG 2.1.4) persiste no SpinePreferencesService. -->\n <hm-shortcut-list class=\"sp-settings__shortcuts\" characterKeysToggle [registered]=\"false\" [items]=\"shortcutItems()\" [headingLevel]=\"4\" />\n </hm-settings-section>\n\n <!-- \u2500\u2500 Dados do navegador \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Listas por usu\u00E1rio do SpineUserStorage e as prefer\u00EAncias deste painel. Destrutivo: o 1\u00BA clique pede\n confirma\u00E7\u00E3o (bot\u00E3o vira \"Confirmar\" + Cancelar), como nas listas de tokens e sess\u00F5es. -->\n <hm-settings-section [key]=\"keys.data\" [label]=\"text.browserData\" icon=\"browser-setting\" [description]=\"text.browserDataDescription\" [keywords]=\"text.browserDataKeywords\">\n <!-- SpineFeatures.accountPreferences: as prefer\u00EAncias tamb\u00E9m na conta do Argos (as telas acima n\u00E3o mudam). -->\n @if (features.accountPreferences) {\n <hm-settings-row [label]=\"featureText.sync\" [description]=\"syncStatus() || featureText.syncDescription\" [keywords]=\"featureText.syncKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: featureText.sync })\" [text]=\"featureText.syncDescription\" />\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"accountPreferences.status() === 'local'\" [loading]=\"accountPreferences.status() === 'saving' || accountPreferences.status() === 'loading'\" (clicked)=\"syncNow()\">{{ featureText.syncNow }}</hm-button>\n </hm-settings-row>\n }\n @for (list of browserLists(); track list.id) {\n <hm-settings-row [label]=\"list.label\" [description]=\"list.count\" [keywords]=\"list.keywords\">\n <span class=\"sp-settings__actions\">\n @if (confirming() === list.id) {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button\n [emphasis]=\"confirming() === list.id ? 'high' : 'medium'\"\n intent=\"danger\"\n [disabled]=\"list.empty && confirming() !== list.id\"\n (clicked)=\"clearList(list.id)\"\n >{{ confirming() === list.id ? text.confirmClear : text.clear }}</hm-button>\n </span>\n </hm-settings-row>\n }\n\n <hm-settings-row [label]=\"text.restorePreferences\" [description]=\"text.restorePreferencesDescription\" [keywords]=\"text.restorePreferencesKeywords\">\n <hm-toggletip rowHelp [label]=\"fmt(text.moreAbout, { label: text.restorePreferences })\" [text]=\"text.restorePreferencesHelp\" />\n <span class=\"sp-settings__actions\">\n @if (confirming() === 'preferences') {\n <hm-button emphasis=\"medium\" (clicked)=\"cancelConfirm()\">{{ text.cancel }}</hm-button>\n }\n <hm-button [emphasis]=\"confirming() === 'preferences' ? 'high' : 'medium'\" intent=\"danger\" (clicked)=\"restorePreferences()\">\n {{ confirming() === 'preferences' ? text.confirmRestore : text.restore }}\n </hm-button>\n </span>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Ajuda \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <!-- Novidades (s\u00F3 com alguma vigente), configura\u00E7\u00E3o inicial e o essencial: fecham o painel antes de abrir. -->\n <hm-settings-section [key]=\"keys.help\" [label]=\"text.help\" icon=\"help\" [keywords]=\"text.helpKeywords\">\n @if (hasNews()) {\n <hm-settings-row [label]=\"text.news\" [description]=\"text.newsDescription\" [keywords]=\"text.newsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"star\" (clicked)=\"showNews()\">{{ text.showNews }}</hm-button>\n </hm-settings-row>\n }\n @if (setupEnabled) {\n <hm-settings-row [label]=\"text.setup\" [description]=\"text.setupDescription\" [keywords]=\"text.setupKeywords\">\n <hm-button emphasis=\"medium\" icon=\"color-palette\" (clicked)=\"redoSetup()\">{{ text.redoSetup }}</hm-button>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.essentials\" [description]=\"text.essentialsDescription\" [keywords]=\"text.essentialsKeywords\">\n <hm-button emphasis=\"medium\" icon=\"help\" (clicked)=\"showEssentials()\">{{ text.showEssentials }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n\n <!-- \u2500\u2500 Se\u00E7\u00F5es do app (provideSettingsSection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (s of appSections; track s.key) {\n <hm-settings-section [key]=\"s.key\" [label]=\"s.label\" [icon]=\"s.icon ?? 'cog'\" [description]=\"s.description ?? ''\" [keywords]=\"s.keywords ?? ''\">\n <ng-container *ngComponentOutlet=\"s.component\" />\n </hm-settings-section>\n }\n\n <!-- \u2500\u2500 Sobre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <hm-settings-section [key]=\"keys.about\" [label]=\"text.about\" icon=\"info-circle\" [keywords]=\"text.aboutKeywords\">\n <hm-settings-row [label]=\"text.application\">\n <span class=\"sp-settings__value\">{{ appName }}@if (packageInfo.name) { <span class=\"sp-settings__muted\"> \u00B7 {{ packageInfo.name }}</span> }</span>\n </hm-settings-row>\n <!-- M\u00E9dia da ferramenta (SpineFeatures.ratings) e \"Avaliar\", que abre o di\u00E1logo da estrela do cabe\u00E7alho. -->\n @if (ratingsEnabled) {\n <hm-settings-row [label]=\"ratingText.aboutRating\" [keywords]=\"ratingText.aboutRatingKeywords\">\n <spine-app-rating-summary />\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.version\" [keywords]=\"text.versionKeywords\">\n <span class=\"sp-settings__value sp-settings__mono\">{{ packageInfo.version ?? '\u2014' }}</span>\n </hm-settings-row>\n @if (buildDate) {\n <hm-settings-row [label]=\"text.buildDate\">\n <span class=\"sp-settings__value\">{{ buildDate }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.environment) {\n <hm-settings-row [label]=\"text.environment\" [keywords]=\"text.environmentKeywords\">\n <span class=\"sp-settings__value\">{{ packageInfo.environment }}</span>\n </hm-settings-row>\n }\n @if (packageInfo.repository?.url || packageInfo.docs || packageInfo.issues) {\n <hm-settings-row [label]=\"text.links\" [keywords]=\"text.linksKeywords\">\n <span class=\"sp-settings__links\">\n @if (packageInfo.docs) { <hm-button emphasis=\"medium\" icon=\"book\" [link]=\"packageInfo.docs\" [newTab]=\"true\">{{ text.docs }}</hm-button> }\n @if (packageInfo.issues) { <hm-button emphasis=\"medium\" icon=\"bug\" [link]=\"packageInfo.issues\" [newTab]=\"true\">{{ text.reportIssue }}</hm-button> }\n @if (packageInfo.repository?.url) { <hm-button emphasis=\"medium\" icon=\"link\" [link]=\"packageInfo.repository!.url!\" [newTab]=\"true\">{{ text.repository }}</hm-button> }\n </span>\n </hm-settings-row>\n }\n @if (dependencies.length) {\n <hm-settings-row [label]=\"text.dependencies\" [description]=\"text.dependenciesDescription\" [keywords]=\"text.dependenciesKeywords\" [stacked]=\"true\">\n <dl class=\"sp-settings__deps\">\n @for (d of dependencies; track d.name) {\n <div><dt>{{ d.label }}</dt><dd class=\"sp-settings__mono\">{{ d.version }}</dd></div>\n }\n </dl>\n </hm-settings-row>\n }\n <hm-settings-row [label]=\"text.supportInfo\" [description]=\"text.supportInfoDescription\" [keywords]=\"text.supportInfoKeywords\">\n <hm-button emphasis=\"medium\" icon=\"clipboard\" (clicked)=\"copySupportInfo()\">{{ text.copy }}</hm-button>\n </hm-settings-row>\n </hm-settings-section>\n </hm-settings-dialog>\n}\n", styles: ["@charset \"UTF-8\";.sp-settings__timezone{display:block;width:min(320px,100%)}.sp-settings__profile{display:flex;align-items:center;gap:var(--hm-space-sm);padding:var(--hm-space-xs) 0 var(--hm-space-sm)}.sp-settings__profile-text{display:flex;min-width:0;flex-direction:column;gap:var(--hm-space-micro);color:var(--hm-text);overflow-wrap:anywhere}.sp-settings__profile-text span{color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.sp-settings__value{color:var(--hm-text);font-size:var(--hm-font-size-sm);text-align:end;overflow-wrap:anywhere}.sp-settings__muted{color:var(--hm-muted)}.sp-settings__mono{font-family:var(--hm-font-mono)}.sp-settings__shortcuts{display:block;padding-block:var(--hm-space-xxxs);border-top:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__links,.sp-settings__actions{display:inline-flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--hm-space-xs)}.sp-settings__section--group-open ::ng-deep .hm-settings-section__heading{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__picker ::ng-deep .hm-theme-picker__title{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.sp-settings__deps{display:grid;flex:1;min-width:0;grid-template-columns:repeat(auto-fill,minmax(min(12rem,100%),1fr));margin:0;overflow:hidden;border:var(--hm-border-hairline) solid var(--hm-border);border-radius:var(--hm-radius);background:var(--hm-surface)}.sp-settings__deps div{display:flex;justify-content:space-between;gap:var(--hm-space-xs);margin-block-start:calc(-1 * var(--hm-border-hairline));margin-inline-start:calc(-1 * var(--hm-border-hairline));padding:var(--hm-space-xs) var(--hm-space-sm);border-top:var(--hm-border-hairline) solid var(--hm-divider);border-inline-start:var(--hm-border-hairline) solid var(--hm-divider)}.sp-settings__deps dt{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.sp-settings__deps dd{margin:0;color:var(--hm-text);font-size:var(--hm-font-size-xs)}\n"] }]
6522
7084
  }], ctorParameters: () => [] });
6523
7085
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6524
7086
  /** "UTC−03:00" para um fuso IANA. */
@@ -6996,7 +7558,7 @@ function registerPage() {
6996
7558
  route_redirect: SPINE_SUGGESTIONS_PAGE_PATH,
6997
7559
  description: text.description,
6998
7560
  },
6999
- load: () => import('./trismegisto-spine-layout-suggestions-page.component-CEKMqera.mjs').then((m) => m.SpineSuggestionsPageComponent),
7561
+ load: () => import('./trismegisto-spine-layout-suggestions-page.component-Bh3VGlOR.mjs').then((m) => m.SpineSuggestionsPageComponent),
7000
7562
  });
7001
7563
  });
7002
7564
  }
@@ -8101,6 +8663,394 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
8101
8663
  `, styles: [":host{display:block;flex:none}\n"] }]
8102
8664
  }] });
8103
8665
 
8666
+ const PANEL_ID = 'spine-ai-agent-panel';
8667
+ const TITLE_ID = 'spine-ai-agent-title';
8668
+ const PANEL_STATE_KEY = 'spine-ai-agent:panel';
8669
+ // Em px, medidas do ponteiro: até aqui é um clique, e o balão não encosta na borda da tela.
8670
+ const DRAG_THRESHOLD = 4;
8671
+ const DRAG_MARGIN = 8;
8672
+ const GREETING_KEY = 'spine-ai-agent:greeting-closed';
8673
+ const GREETING_MS = 15_000;
8674
+ const GREETING_SUGGESTIONS = 3;
8675
+ function greetingClosed() {
8676
+ try {
8677
+ return sessionStorage.getItem(GREETING_KEY) === '1';
8678
+ }
8679
+ catch {
8680
+ return false;
8681
+ }
8682
+ }
8683
+ function readPanelState() {
8684
+ try {
8685
+ return JSON.parse(sessionStorage.getItem(PANEL_STATE_KEY) ?? '{}');
8686
+ }
8687
+ catch {
8688
+ return {};
8689
+ }
8690
+ }
8691
+ /**
8692
+ * O agente de IA do app: um balão flutuante, que abre a conversa num painel ao lado dele. O painel não é modal, para a
8693
+ * pessoa continuar vendo e usando a tela enquanto pergunta. Esc fecha e devolve o foco a quem abriu, e o painel pode ser
8694
+ * expandido para respostas longas. Aberto e tamanho voltam como estavam ao recarregar a aba. A tela da conversa fica em chunk
8695
+ * próprio, carregado na primeira abertura, e continua montada depois disso.
8696
+ *
8697
+ * O balão pode ser arrastado e, pelo X dele, sair da tela: vira um ícone no cabeçalho (`spine-ai-agent-dock-button`), e o
8698
+ * painel passa a abrir logo abaixo do cabeçalho. Onde ele está vale neste navegador. Ao lado do balão aparece um convite com
8699
+ * o que dá para perguntar, uma vez por aba, que sai sozinho ou quando a pessoa o fecha. Sem `SpineFeatures.aiAgent`, não
8700
+ * desenha nada.
8701
+ */
8702
+ class SpineAiAgentLauncherComponent {
8703
+ constructor() {
8704
+ this.agent = inject(SpineAiAgentService);
8705
+ this.text = inject(SPINE_MESSAGES).aiAgentChat;
8706
+ this.panelId = PANEL_ID;
8707
+ this.titleId = TITLE_ID;
8708
+ /** Tamanho do painel: continua como a pessoa deixou ao fechar e abrir. */
8709
+ this.expanded = signal(readPanelState().expanded === true, ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
8710
+ this.chat = signal(null, ...(ngDevMode ? [{ debugName: "chat" }] : /* istanbul ignore next */ []));
8711
+ this.dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
8712
+ /** Com o balão na metade de cima da tela, o painel abre para baixo, onde há espaço. */
8713
+ this.below = computed(() => !this.agent.docked() && (this.agent.position()?.bottom ?? 0) > window.innerHeight / 2, ...(ngDevMode ? [{ debugName: "below" }] : /* istanbul ignore next */ []));
8714
+ /** O balão é um círculo: o rosto cabe nele, e uma imagem de corpo inteiro cortaria onde não deve. */
8715
+ this.fabImage = computed(() => this.agent.avatarNow() ?? this.agent.image(), ...(ngDevMode ? [{ debugName: "fabImage" }] : /* istanbul ignore next */ []));
8716
+ /** Com o balão na metade inicial da tela, o convite fica depois dele. */
8717
+ this.greetingAfter = computed(() => (this.agent.position()?.end ?? 0) > window.innerWidth / 2, ...(ngDevMode ? [{ debugName: "greetingAfter" }] : /* istanbul ignore next */ []));
8718
+ this.greetingOpen = signal(!greetingClosed(), ...(ngDevMode ? [{ debugName: "greetingOpen" }] : /* istanbul ignore next */ []));
8719
+ this.greetingVisible = computed(() => this.greetingOpen() && !this.agent.panelOpen() && !this.agent.docked() && !this.dragging(), ...(ngDevMode ? [{ debugName: "greetingVisible" }] : /* istanbul ignore next */ []));
8720
+ this.greetingSuggestions = computed(() => this.agent.suggestions().slice(0, GREETING_SUGGESTIONS), ...(ngDevMode ? [{ debugName: "greetingSuggestions" }] : /* istanbul ignore next */ []));
8721
+ this.host = inject(ElementRef);
8722
+ this.injector = inject(Injector);
8723
+ this.document = inject(DOCUMENT);
8724
+ /** O clique que encerra um arraste não abre o painel. */
8725
+ this.dragged = false;
8726
+ this.stopDrag = null;
8727
+ this.greetingTimer = null;
8728
+ inject(DestroyRef).onDestroy(() => {
8729
+ this.stopDrag?.();
8730
+ this.keepGreeting();
8731
+ });
8732
+ effect(() => {
8733
+ const state = JSON.stringify({ open: this.agent.panelOpen(), expanded: this.expanded() });
8734
+ try {
8735
+ sessionStorage.setItem(PANEL_STATE_KEY, state);
8736
+ }
8737
+ catch {
8738
+ // Navegador sem armazenamento: o painel só não volta aberto.
8739
+ }
8740
+ });
8741
+ // O painel também abre pelo ícone do cabeçalho, que está em outro componente.
8742
+ effect(() => {
8743
+ if (this.agent.panelOpen())
8744
+ untracked(() => void this.prepare());
8745
+ });
8746
+ }
8747
+ ngOnInit() {
8748
+ // Sem roubar o foco: a pessoa acabou de recarregar a tela, não pediu o chat agora.
8749
+ if (this.agent.enabled && readPanelState().open === true)
8750
+ this.agent.panelOpen.set(true);
8751
+ if (this.agent.enabled && this.greetingVisible()) {
8752
+ this.agent.loadSuggestions();
8753
+ this.greetingTimer = setTimeout(() => this.closeGreeting(), GREETING_MS);
8754
+ }
8755
+ }
8756
+ toggle() {
8757
+ if (!this.dragged)
8758
+ this.agent.togglePanel();
8759
+ }
8760
+ async prepare() {
8761
+ const focus = this.agent.focusOnOpen;
8762
+ this.agent.focusOnOpen = false;
8763
+ if (!this.chat()) {
8764
+ const { SpineAiAgentChatComponent } = await import('./trismegisto-spine-layout-ai-agent-chat.component-C5JljPYD.mjs');
8765
+ this.chat.set(SpineAiAgentChatComponent);
8766
+ }
8767
+ this.agent.loadSuggestions();
8768
+ this.agent.restoreConversation();
8769
+ if (focus)
8770
+ afterNextRender(() => this.host.nativeElement.querySelector('textarea')?.focus(), { injector: this.injector });
8771
+ }
8772
+ /** Quem está lendo ou usando o convite decide quando ele sai. */
8773
+ keepGreeting() {
8774
+ if (this.greetingTimer)
8775
+ clearTimeout(this.greetingTimer);
8776
+ this.greetingTimer = null;
8777
+ }
8778
+ closeGreeting() {
8779
+ this.keepGreeting();
8780
+ this.greetingOpen.set(false);
8781
+ try {
8782
+ sessionStorage.setItem(GREETING_KEY, '1');
8783
+ }
8784
+ catch {
8785
+ // Navegador sem armazenamento: o convite só volta ao recarregar.
8786
+ }
8787
+ }
8788
+ askFromGreeting(question) {
8789
+ this.closeGreeting();
8790
+ this.agent.askFromGreeting(question);
8791
+ }
8792
+ close() {
8793
+ this.agent.panelOpen.set(false);
8794
+ this.focusEntry();
8795
+ }
8796
+ dock() {
8797
+ this.agent.dock();
8798
+ afterNextRender(() => this.focusEntry(), { injector: this.injector });
8799
+ }
8800
+ undock() {
8801
+ this.agent.undock();
8802
+ afterNextRender(() => this.focusEntry(), { injector: this.injector });
8803
+ }
8804
+ /** O foco volta a quem abre o painel: o balão ou, com ele fora da tela, o ícone do cabeçalho. */
8805
+ focusEntry() {
8806
+ const entry = this.agent.docked()
8807
+ ? this.document.querySelector(`.${SPINE_AGENT_DOCK_CLASS} button`)
8808
+ : this.host.nativeElement.querySelector('.hm-fab__button');
8809
+ entry?.focus();
8810
+ }
8811
+ /** Só passa a ser arraste depois de `DRAG_THRESHOLD`; antes disso, soltar é o clique normal do balão. */
8812
+ startDrag(event) {
8813
+ if (event.button !== 0)
8814
+ return;
8815
+ this.dragged = false;
8816
+ const box = event.currentTarget.getBoundingClientRect();
8817
+ const rtl = getComputedStyle(event.currentTarget).direction === 'rtl';
8818
+ const { clientX: startX, clientY: startY } = event;
8819
+ const view = this.document.defaultView;
8820
+ const move = (moved) => {
8821
+ const dx = moved.clientX - startX;
8822
+ const dy = moved.clientY - startY;
8823
+ if (!this.dragged && Math.hypot(dx, dy) < DRAG_THRESHOLD)
8824
+ return;
8825
+ this.dragged = true;
8826
+ this.dragging.set(true);
8827
+ const left = Math.min(Math.max(box.left + dx, DRAG_MARGIN), view.innerWidth - box.width - DRAG_MARGIN);
8828
+ const top = Math.min(Math.max(box.top + dy, DRAG_MARGIN), view.innerHeight - box.height - DRAG_MARGIN);
8829
+ this.agent.position.set({ end: rtl ? left : view.innerWidth - left - box.width, bottom: view.innerHeight - top - box.height });
8830
+ };
8831
+ const stop = () => {
8832
+ this.stopDrag = null;
8833
+ this.document.removeEventListener('pointermove', move);
8834
+ this.document.removeEventListener('pointerup', stop);
8835
+ this.document.removeEventListener('pointercancel', stop);
8836
+ this.dragging.set(false);
8837
+ if (this.dragged)
8838
+ this.agent.savePlacement();
8839
+ };
8840
+ this.document.addEventListener('pointermove', move);
8841
+ this.document.addEventListener('pointerup', stop);
8842
+ this.document.addEventListener('pointercancel', stop);
8843
+ this.stopDrag = stop;
8844
+ }
8845
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentLauncherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8846
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SpineAiAgentLauncherComponent, isStandalone: true, selector: "spine-ai-agent-launcher", host: { properties: { "class.spine-agent--docked": "agent.docked()", "class.spine-agent--below": "below()", "style.--spine-agent-end": "agent.position() ? agent.position()!.end + 'px' : null", "style.--spine-agent-bottom": "agent.position() ? agent.position()!.bottom + 'px' : null" } }, ngImport: i0, template: `
8847
+ @if (agent.enabled) {
8848
+ @if (chat(); as component) {
8849
+ <section
8850
+ class="spine-agent-panel"
8851
+ role="dialog"
8852
+ aria-modal="false"
8853
+ [attr.id]="panelId"
8854
+ [attr.aria-labelledby]="titleId"
8855
+ [class.spine-agent-panel--open]="agent.panelOpen()"
8856
+ [class.spine-agent-panel--expanded]="expanded()"
8857
+ [hidden]="!agent.panelOpen()"
8858
+ (keydown.escape)="close()"
8859
+ >
8860
+ <header class="spine-agent-panel__header">
8861
+ @if (agent.avatarNow(); as avatar) {
8862
+ <img class="spine-agent-panel__avatar" [src]="avatar" alt="" (error)="agent.imageFailed(avatar)" />
8863
+ }
8864
+ <h2 class="spine-agent-panel__title" [attr.id]="titleId">{{ agent.title }}</h2>
8865
+ <hm-icon-button
8866
+ emphasis="subtle"
8867
+ size="small"
8868
+ icon="plus"
8869
+ [label]="text.newConversation"
8870
+ [disabled]="agent.running() || !agent.entries().length"
8871
+ (clicked)="agent.reset()"
8872
+ />
8873
+ <hm-icon-button
8874
+ emphasis="subtle"
8875
+ size="small"
8876
+ [icon]="expanded() ? 'window-restore' : 'window-max'"
8877
+ [label]="expanded() ? text.collapse : text.expand"
8878
+ (clicked)="expanded.set(!expanded())"
8879
+ />
8880
+ @if (agent.docked()) {
8881
+ <hm-icon-button emphasis="subtle" size="small" icon="unpin" [label]="text.undock" (clicked)="undock()" />
8882
+ }
8883
+ <hm-icon-button emphasis="subtle" size="small" icon="times" [label]="text.close" (clicked)="close()" />
8884
+ </header>
8885
+ <div class="spine-agent-panel__body">
8886
+ <ng-container *ngComponentOutlet="component" />
8887
+ </div>
8888
+ </section>
8889
+ }
8890
+ @if (!agent.docked()) {
8891
+ <div class="spine-agent-fab" [class.spine-agent-fab--dragging]="dragging()" (pointerdown)="startDrag($event)">
8892
+ @if (greetingVisible()) {
8893
+ <section
8894
+ class="spine-agent-greeting"
8895
+ [class.spine-agent-greeting--after]="greetingAfter()"
8896
+ [class.spine-agent-greeting--below]="below()"
8897
+ [class.spine-agent-greeting--figure]="agent.image()"
8898
+ [attr.aria-label]="agent.title"
8899
+ (pointerdown)="$event.stopPropagation()"
8900
+ (mouseenter)="keepGreeting()"
8901
+ (focusin)="keepGreeting()"
8902
+ >
8903
+ @if (agent.image(); as figure) {
8904
+ <img class="spine-agent-greeting__figure" [src]="figure" alt="" (error)="agent.imageFailed(figure)" />
8905
+ }
8906
+ <div class="spine-agent-greeting__content">
8907
+ <div class="spine-agent-greeting__head">
8908
+ <p class="spine-agent-greeting__title">{{ agent.greetingTitle }}</p>
8909
+ <hm-icon-button emphasis="subtle" size="small" icon="times" [label]="text.greetingClose" (clicked)="closeGreeting()" />
8910
+ </div>
8911
+ <p class="spine-agent-greeting__text">{{ agent.greetingText }}</p>
8912
+ @if (greetingSuggestions().length) {
8913
+ <hm-prompt-suggestions [suggestions]="greetingSuggestions()" (chosen)="askFromGreeting($event)" />
8914
+ }
8915
+ </div>
8916
+ </section>
8917
+ }
8918
+ <hm-fab
8919
+ [fixed]="false"
8920
+ [label]="agent.panelOpen() ? text.close : agent.title"
8921
+ [icon]="agent.panelOpen() ? 'times' : fabImage() ? '' : 'chat-bubble'"
8922
+ [expanded]="agent.panelOpen()"
8923
+ [controls]="panelId"
8924
+ avoid=""
8925
+ (triggered)="toggle()"
8926
+ />
8927
+ @if (!agent.panelOpen() && fabImage(); as image) {
8928
+ <img class="spine-agent-fab__image" [src]="image" alt="" (error)="agent.imageFailed(image)" />
8929
+ }
8930
+ @if (!agent.panelOpen()) {
8931
+ <hm-icon-button
8932
+ class="spine-agent-fab__dock"
8933
+ emphasis="outlined"
8934
+ size="small"
8935
+ icon="times"
8936
+ [label]="text.dock"
8937
+ (pointerdown)="$event.stopPropagation()"
8938
+ (clicked)="dock()"
8939
+ />
8940
+ }
8941
+ </div>
8942
+ }
8943
+ }
8944
+ `, isInline: true, styles: [":host{--spine-agent-fab-size: calc(var(--hm-control-height-large) * 1.75);--spine-agent-end-now: var(--spine-agent-end, var(--hm-space-lg));--spine-agent-bottom-now: var(--spine-agent-bottom, var(--hm-space-lg));--spine-agent-panel-inline: min(calc(var(--hm-space-3xl) * 11), calc(100vw - 2 * var(--hm-space-md)));--spine-agent-offset: calc(var(--spine-agent-bottom-now) + var(--spine-agent-fab-size) + var(--hm-space-sm));--spine-agent-room: calc(100dvh - var(--spine-agent-offset) - var(--hm-header-height) - var(--hm-space-xs))}:host(.spine-agent--below){--spine-agent-offset: calc(100dvh - var(--spine-agent-bottom-now) + var(--hm-space-sm));--spine-agent-room: calc(100dvh - var(--spine-agent-offset) - var(--hm-space-md))}:host(.spine-agent--docked){--spine-agent-offset: calc(var(--hm-header-height) + var(--hm-space-xs));--spine-agent-room: calc(100dvh - var(--spine-agent-offset) - var(--hm-space-md))}.spine-agent-fab{position:fixed;inset-inline-end:min(var(--spine-agent-end-now),calc(100vw - var(--spine-agent-fab-size) - var(--hm-space-xs)));inset-block-end:calc(min(var(--spine-agent-bottom-now),calc(100dvh - var(--spine-agent-fab-size) - var(--hm-space-xs))) + env(safe-area-inset-bottom,0px));z-index:var(--hm-z-fixed);inline-size:var(--spine-agent-fab-size);block-size:var(--spine-agent-fab-size);cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.spine-agent-fab--dragging{cursor:grabbing}.spine-agent-fab__image{position:absolute;inset:0;inline-size:100%;block-size:100%;object-fit:cover;border-radius:var(--hm-radius-pill);pointer-events:none}.spine-agent-greeting{position:absolute;inset-block-end:0;inset-inline-end:calc(100% + var(--hm-space-sm));display:flex;align-items:flex-end;gap:var(--hm-space-sm);box-sizing:border-box;inline-size:min(calc(var(--hm-space-3xl) * 7),calc(100vw - var(--spine-agent-fab-size) - 2 * var(--hm-space-lg)));overflow:hidden;padding:var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-md);background:var(--hm-surface-raised);color:var(--hm-text);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:calc(var(--hm-radius) * 2);box-shadow:var(--hm-shadow-overlay);cursor:auto;touch-action:auto;-webkit-user-select:text;user-select:text;animation:spine-agent-panel-in var(--hm-duration-quick) var(--hm-ease-decelerate)}.spine-agent-greeting--figure{inline-size:min(calc(var(--hm-space-3xl) * 11),calc(100vw - var(--spine-agent-fab-size) - 2 * var(--hm-space-lg)))}.spine-agent-greeting__figure{flex:none;inline-size:calc(var(--hm-space-3xl) * 3.5);margin-block-end:calc(var(--hm-space-sm) * -1);object-fit:contain;object-position:bottom}.spine-agent-greeting__content{display:flex;flex:1;flex-direction:column;align-self:stretch;gap:var(--hm-space-xs);min-inline-size:0}.spine-agent-greeting--after{inset-inline:calc(100% + var(--hm-space-sm)) auto}.spine-agent-greeting--below{inset-block:0 auto}.spine-agent-greeting__head{display:flex;align-items:center;gap:var(--hm-space-xs)}.spine-agent-greeting__title{flex:1;min-inline-size:0;margin:0;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.spine-agent-greeting__text{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.spine-agent-fab--dragging hm-fab{pointer-events:none}.spine-agent-fab__dock{--hm-radius-control: var(--hm-radius-pill);position:absolute;inset-block-start:calc(var(--hm-space-xs) * -1);inset-inline-end:calc(var(--hm-space-xs) * -1);background:var(--hm-surface-raised);border-radius:var(--hm-radius-pill);box-shadow:var(--hm-shadow-overlay);opacity:0;transition:opacity var(--hm-duration-quick) var(--hm-ease)}.spine-agent-fab:hover .spine-agent-fab__dock,.spine-agent-fab:focus-within .spine-agent-fab__dock{opacity:1}@media(hover:none){.spine-agent-fab__dock{opacity:1}}.spine-agent-panel{position:fixed;inset-inline-end:clamp(var(--hm-space-md),var(--spine-agent-end-now),calc(100vw - var(--spine-agent-panel-inline) - var(--hm-space-md)));inset-block-end:calc(var(--spine-agent-offset) + env(safe-area-inset-bottom,0px));z-index:var(--hm-z-fixed);display:flex;flex-direction:column;inline-size:var(--spine-agent-panel-inline);block-size:min(calc(var(--hm-space-3xl) * 16),var(--spine-agent-room));overflow:hidden;background:var(--hm-surface-raised);color:var(--hm-text);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:calc(var(--hm-radius) * 2);box-shadow:var(--hm-shadow-overlay)}.spine-agent-panel[hidden]{display:none}:host(.spine-agent--below) .spine-agent-panel,:host(.spine-agent--docked) .spine-agent-panel{inset-block-start:var(--spine-agent-offset);inset-block-end:auto}:host(.spine-agent--docked) .spine-agent-panel{inset-inline-end:var(--hm-space-md)}.spine-agent-panel--expanded{--spine-agent-panel-inline: min(calc(var(--hm-space-3xl) * 24), calc(100vw - 2 * var(--hm-space-md)));block-size:var(--spine-agent-room)}.spine-agent-panel--open{transform-origin:bottom right;animation:spine-agent-panel-in var(--hm-duration-quick) var(--hm-ease-decelerate)}:host(.spine-agent--below) .spine-agent-panel--open,:host(.spine-agent--docked) .spine-agent-panel--open{transform-origin:top right}@keyframes spine-agent-panel-in{0%{opacity:0;transform:translateY(var(--hm-space-sm)) scale(.97)}}@media(prefers-reduced-motion:reduce){.spine-agent-panel--open,.spine-agent-greeting{animation:none}.spine-agent-fab__dock{transition:none}}.spine-agent-panel__header{display:flex;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-md);border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}.spine-agent-panel__avatar{flex:none;inline-size:var(--hm-control-height);block-size:var(--hm-control-height);object-fit:cover;border-radius:var(--hm-radius-pill)}.spine-agent-panel__title{flex:1;min-inline-size:0;margin:0;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.spine-agent-panel__body{flex:1;min-block-size:0;padding:var(--hm-space-md)}@media(max-width:600px){:host{--spine-agent-panel-inline: calc(100vw - 2 * var(--hm-space-xs))}.spine-agent-greeting__figure{display:none}.spine-agent-panel,:host(.spine-agent--docked) .spine-agent-panel{inset-inline-end:var(--hm-space-xs)}}\n"], dependencies: [{ kind: "component", type: FabComponent, selector: "hm-fab", inputs: ["label", "icon", "extended", "position", "fixed", "size", "disabled", "highlight", "actions", "hideOnScroll", "avoid", "expanded", "controls"], outputs: ["triggered", "chosen"] }, { kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: PromptSuggestionsComponent, selector: "hm-prompt-suggestions", inputs: ["suggestions", "label", "disabled"], outputs: ["chosen"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8945
+ }
8946
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentLauncherComponent, decorators: [{
8947
+ type: Component,
8948
+ args: [{ selector: 'spine-ai-agent-launcher', imports: [FabComponent, IconButtonComponent, NgComponentOutlet, PromptSuggestionsComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
8949
+ '[class.spine-agent--docked]': 'agent.docked()',
8950
+ '[class.spine-agent--below]': 'below()',
8951
+ '[style.--spine-agent-end]': "agent.position() ? agent.position()!.end + 'px' : null",
8952
+ '[style.--spine-agent-bottom]': "agent.position() ? agent.position()!.bottom + 'px' : null",
8953
+ }, template: `
8954
+ @if (agent.enabled) {
8955
+ @if (chat(); as component) {
8956
+ <section
8957
+ class="spine-agent-panel"
8958
+ role="dialog"
8959
+ aria-modal="false"
8960
+ [attr.id]="panelId"
8961
+ [attr.aria-labelledby]="titleId"
8962
+ [class.spine-agent-panel--open]="agent.panelOpen()"
8963
+ [class.spine-agent-panel--expanded]="expanded()"
8964
+ [hidden]="!agent.panelOpen()"
8965
+ (keydown.escape)="close()"
8966
+ >
8967
+ <header class="spine-agent-panel__header">
8968
+ @if (agent.avatarNow(); as avatar) {
8969
+ <img class="spine-agent-panel__avatar" [src]="avatar" alt="" (error)="agent.imageFailed(avatar)" />
8970
+ }
8971
+ <h2 class="spine-agent-panel__title" [attr.id]="titleId">{{ agent.title }}</h2>
8972
+ <hm-icon-button
8973
+ emphasis="subtle"
8974
+ size="small"
8975
+ icon="plus"
8976
+ [label]="text.newConversation"
8977
+ [disabled]="agent.running() || !agent.entries().length"
8978
+ (clicked)="agent.reset()"
8979
+ />
8980
+ <hm-icon-button
8981
+ emphasis="subtle"
8982
+ size="small"
8983
+ [icon]="expanded() ? 'window-restore' : 'window-max'"
8984
+ [label]="expanded() ? text.collapse : text.expand"
8985
+ (clicked)="expanded.set(!expanded())"
8986
+ />
8987
+ @if (agent.docked()) {
8988
+ <hm-icon-button emphasis="subtle" size="small" icon="unpin" [label]="text.undock" (clicked)="undock()" />
8989
+ }
8990
+ <hm-icon-button emphasis="subtle" size="small" icon="times" [label]="text.close" (clicked)="close()" />
8991
+ </header>
8992
+ <div class="spine-agent-panel__body">
8993
+ <ng-container *ngComponentOutlet="component" />
8994
+ </div>
8995
+ </section>
8996
+ }
8997
+ @if (!agent.docked()) {
8998
+ <div class="spine-agent-fab" [class.spine-agent-fab--dragging]="dragging()" (pointerdown)="startDrag($event)">
8999
+ @if (greetingVisible()) {
9000
+ <section
9001
+ class="spine-agent-greeting"
9002
+ [class.spine-agent-greeting--after]="greetingAfter()"
9003
+ [class.spine-agent-greeting--below]="below()"
9004
+ [class.spine-agent-greeting--figure]="agent.image()"
9005
+ [attr.aria-label]="agent.title"
9006
+ (pointerdown)="$event.stopPropagation()"
9007
+ (mouseenter)="keepGreeting()"
9008
+ (focusin)="keepGreeting()"
9009
+ >
9010
+ @if (agent.image(); as figure) {
9011
+ <img class="spine-agent-greeting__figure" [src]="figure" alt="" (error)="agent.imageFailed(figure)" />
9012
+ }
9013
+ <div class="spine-agent-greeting__content">
9014
+ <div class="spine-agent-greeting__head">
9015
+ <p class="spine-agent-greeting__title">{{ agent.greetingTitle }}</p>
9016
+ <hm-icon-button emphasis="subtle" size="small" icon="times" [label]="text.greetingClose" (clicked)="closeGreeting()" />
9017
+ </div>
9018
+ <p class="spine-agent-greeting__text">{{ agent.greetingText }}</p>
9019
+ @if (greetingSuggestions().length) {
9020
+ <hm-prompt-suggestions [suggestions]="greetingSuggestions()" (chosen)="askFromGreeting($event)" />
9021
+ }
9022
+ </div>
9023
+ </section>
9024
+ }
9025
+ <hm-fab
9026
+ [fixed]="false"
9027
+ [label]="agent.panelOpen() ? text.close : agent.title"
9028
+ [icon]="agent.panelOpen() ? 'times' : fabImage() ? '' : 'chat-bubble'"
9029
+ [expanded]="agent.panelOpen()"
9030
+ [controls]="panelId"
9031
+ avoid=""
9032
+ (triggered)="toggle()"
9033
+ />
9034
+ @if (!agent.panelOpen() && fabImage(); as image) {
9035
+ <img class="spine-agent-fab__image" [src]="image" alt="" (error)="agent.imageFailed(image)" />
9036
+ }
9037
+ @if (!agent.panelOpen()) {
9038
+ <hm-icon-button
9039
+ class="spine-agent-fab__dock"
9040
+ emphasis="outlined"
9041
+ size="small"
9042
+ icon="times"
9043
+ [label]="text.dock"
9044
+ (pointerdown)="$event.stopPropagation()"
9045
+ (clicked)="dock()"
9046
+ />
9047
+ }
9048
+ </div>
9049
+ }
9050
+ }
9051
+ `, styles: [":host{--spine-agent-fab-size: calc(var(--hm-control-height-large) * 1.75);--spine-agent-end-now: var(--spine-agent-end, var(--hm-space-lg));--spine-agent-bottom-now: var(--spine-agent-bottom, var(--hm-space-lg));--spine-agent-panel-inline: min(calc(var(--hm-space-3xl) * 11), calc(100vw - 2 * var(--hm-space-md)));--spine-agent-offset: calc(var(--spine-agent-bottom-now) + var(--spine-agent-fab-size) + var(--hm-space-sm));--spine-agent-room: calc(100dvh - var(--spine-agent-offset) - var(--hm-header-height) - var(--hm-space-xs))}:host(.spine-agent--below){--spine-agent-offset: calc(100dvh - var(--spine-agent-bottom-now) + var(--hm-space-sm));--spine-agent-room: calc(100dvh - var(--spine-agent-offset) - var(--hm-space-md))}:host(.spine-agent--docked){--spine-agent-offset: calc(var(--hm-header-height) + var(--hm-space-xs));--spine-agent-room: calc(100dvh - var(--spine-agent-offset) - var(--hm-space-md))}.spine-agent-fab{position:fixed;inset-inline-end:min(var(--spine-agent-end-now),calc(100vw - var(--spine-agent-fab-size) - var(--hm-space-xs)));inset-block-end:calc(min(var(--spine-agent-bottom-now),calc(100dvh - var(--spine-agent-fab-size) - var(--hm-space-xs))) + env(safe-area-inset-bottom,0px));z-index:var(--hm-z-fixed);inline-size:var(--spine-agent-fab-size);block-size:var(--spine-agent-fab-size);cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.spine-agent-fab--dragging{cursor:grabbing}.spine-agent-fab__image{position:absolute;inset:0;inline-size:100%;block-size:100%;object-fit:cover;border-radius:var(--hm-radius-pill);pointer-events:none}.spine-agent-greeting{position:absolute;inset-block-end:0;inset-inline-end:calc(100% + var(--hm-space-sm));display:flex;align-items:flex-end;gap:var(--hm-space-sm);box-sizing:border-box;inline-size:min(calc(var(--hm-space-3xl) * 7),calc(100vw - var(--spine-agent-fab-size) - 2 * var(--hm-space-lg)));overflow:hidden;padding:var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-md);background:var(--hm-surface-raised);color:var(--hm-text);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:calc(var(--hm-radius) * 2);box-shadow:var(--hm-shadow-overlay);cursor:auto;touch-action:auto;-webkit-user-select:text;user-select:text;animation:spine-agent-panel-in var(--hm-duration-quick) var(--hm-ease-decelerate)}.spine-agent-greeting--figure{inline-size:min(calc(var(--hm-space-3xl) * 11),calc(100vw - var(--spine-agent-fab-size) - 2 * var(--hm-space-lg)))}.spine-agent-greeting__figure{flex:none;inline-size:calc(var(--hm-space-3xl) * 3.5);margin-block-end:calc(var(--hm-space-sm) * -1);object-fit:contain;object-position:bottom}.spine-agent-greeting__content{display:flex;flex:1;flex-direction:column;align-self:stretch;gap:var(--hm-space-xs);min-inline-size:0}.spine-agent-greeting--after{inset-inline:calc(100% + var(--hm-space-sm)) auto}.spine-agent-greeting--below{inset-block:0 auto}.spine-agent-greeting__head{display:flex;align-items:center;gap:var(--hm-space-xs)}.spine-agent-greeting__title{flex:1;min-inline-size:0;margin:0;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.spine-agent-greeting__text{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm)}.spine-agent-fab--dragging hm-fab{pointer-events:none}.spine-agent-fab__dock{--hm-radius-control: var(--hm-radius-pill);position:absolute;inset-block-start:calc(var(--hm-space-xs) * -1);inset-inline-end:calc(var(--hm-space-xs) * -1);background:var(--hm-surface-raised);border-radius:var(--hm-radius-pill);box-shadow:var(--hm-shadow-overlay);opacity:0;transition:opacity var(--hm-duration-quick) var(--hm-ease)}.spine-agent-fab:hover .spine-agent-fab__dock,.spine-agent-fab:focus-within .spine-agent-fab__dock{opacity:1}@media(hover:none){.spine-agent-fab__dock{opacity:1}}.spine-agent-panel{position:fixed;inset-inline-end:clamp(var(--hm-space-md),var(--spine-agent-end-now),calc(100vw - var(--spine-agent-panel-inline) - var(--hm-space-md)));inset-block-end:calc(var(--spine-agent-offset) + env(safe-area-inset-bottom,0px));z-index:var(--hm-z-fixed);display:flex;flex-direction:column;inline-size:var(--spine-agent-panel-inline);block-size:min(calc(var(--hm-space-3xl) * 16),var(--spine-agent-room));overflow:hidden;background:var(--hm-surface-raised);color:var(--hm-text);border:var(--hm-border-hairline) solid var(--hm-divider);border-radius:calc(var(--hm-radius) * 2);box-shadow:var(--hm-shadow-overlay)}.spine-agent-panel[hidden]{display:none}:host(.spine-agent--below) .spine-agent-panel,:host(.spine-agent--docked) .spine-agent-panel{inset-block-start:var(--spine-agent-offset);inset-block-end:auto}:host(.spine-agent--docked) .spine-agent-panel{inset-inline-end:var(--hm-space-md)}.spine-agent-panel--expanded{--spine-agent-panel-inline: min(calc(var(--hm-space-3xl) * 24), calc(100vw - 2 * var(--hm-space-md)));block-size:var(--spine-agent-room)}.spine-agent-panel--open{transform-origin:bottom right;animation:spine-agent-panel-in var(--hm-duration-quick) var(--hm-ease-decelerate)}:host(.spine-agent--below) .spine-agent-panel--open,:host(.spine-agent--docked) .spine-agent-panel--open{transform-origin:top right}@keyframes spine-agent-panel-in{0%{opacity:0;transform:translateY(var(--hm-space-sm)) scale(.97)}}@media(prefers-reduced-motion:reduce){.spine-agent-panel--open,.spine-agent-greeting{animation:none}.spine-agent-fab__dock{transition:none}}.spine-agent-panel__header{display:flex;align-items:center;gap:var(--hm-space-xs);padding:var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-sm) var(--hm-space-md);border-block-end:var(--hm-border-hairline) solid var(--hm-divider)}.spine-agent-panel__avatar{flex:none;inline-size:var(--hm-control-height);block-size:var(--hm-control-height);object-fit:cover;border-radius:var(--hm-radius-pill)}.spine-agent-panel__title{flex:1;min-inline-size:0;margin:0;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}.spine-agent-panel__body{flex:1;min-block-size:0;padding:var(--hm-space-md)}@media(max-width:600px){:host{--spine-agent-panel-inline: calc(100vw - 2 * var(--hm-space-xs))}.spine-agent-greeting__figure{display:none}.spine-agent-panel,:host(.spine-agent--docked) .spine-agent-panel{inset-inline-end:var(--hm-space-xs)}}\n"] }]
9052
+ }], ctorParameters: () => [] });
9053
+
8104
9054
  /**
8105
9055
  * Aviso de "Encerrar por inatividade" (`SpineIdleService`): contagem regressiva, "Continuar conectado" (padrão, e o
8106
9056
  * que fechar pelo X/Esc faz) e "Sair agora". Montado uma vez no `spine-layout`.
@@ -8185,11 +9135,11 @@ class SpineUnsavedChangesService {
8185
9135
  canReplaceActive(panelId) {
8186
9136
  if (!this.enabled)
8187
9137
  return true;
8188
- const active = findPanelTabs$1(this.nav.store.getState().root, panelId)?.activeId;
9138
+ const active = findPanelTabs(this.nav.store.getState().root, panelId)?.activeId;
8189
9139
  return active && this.unsaved.hasUnsavedChanges(active) ? this.unsaved.confirmDiscard() : true;
8190
9140
  }
8191
9141
  guard(store) {
8192
- const tabsOf = (panelId) => findPanelTabs$1(store.getState().root, panelId)?.tabs.map((t) => t.id) ?? [];
9142
+ const tabsOf = (panelId) => findPanelTabs(store.getState().root, panelId)?.tabs.map((t) => t.id) ?? [];
8193
9143
  // Sem alteração em nenhuma das abas, fecha na hora (mesmo comportamento de antes); com alteração, pergunta.
8194
9144
  const closing = (ids, close) => {
8195
9145
  if (!ids.some((id) => this.unsaved.hasUnsavedChanges(id))) {
@@ -8247,7 +9197,7 @@ class LayoutComponent {
8247
9197
  this.preferences = inject(SpinePreferencesService);
8248
9198
  this.workspace = inject(SpineWorkspaceNavService);
8249
9199
  this.settings = inject(SpineSettingsService);
8250
- this.sidebar = inject(HermesSidebarService$1);
9200
+ this.sidebar = inject(HermesSidebarService);
8251
9201
  /** Também liga, uma vez, o aviso de falha do logout (`logoutFailed$` do `@orfeu/angular`). */
8252
9202
  this.logoutNotice = inject(SpineLogoutService);
8253
9203
  /** Recursos opcionais (`SpineConfig.features`): cada serviço não faz nada com o seu recurso desligado. */
@@ -8351,6 +9301,8 @@ class LayoutComponent {
8351
9301
  <!-- Todos os avisos (respostas do servidor, avisos do app, configurações, auto-serviço…). Se o app também montar
8352
9302
  uma, só uma desenha (o serviço escolhe a pilha ativa): nunca em dobro. -->
8353
9303
  <hm-toast-stack />
9304
+ <!-- Agente de IA do app (SpineFeatures.aiAgent): botão flutuante no canto, que abre a conversa. -->
9305
+ <spine-ai-agent-launcher />
8354
9306
  <spine-side-panel />
8355
9307
  <spine-modal />
8356
9308
  <spine-shortcuts-cheatsheet></spine-shortcuts-cheatsheet>
@@ -8363,7 +9315,7 @@ class LayoutComponent {
8363
9315
  }
8364
9316
  <!-- Aviso de "Encerrar por inatividade" (SpineFeatures.idleTimeout): só desenha quando abre. -->
8365
9317
  <spine-idle-warning />
8366
- `, isInline: true, styles: [":host{display:block}.shell{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--hm-surface);color:var(--hm-text)}.body{position:relative;display:flex;flex:1;min-height:0;overflow:hidden}.main{display:flex;flex:1;min-width:0;flex-direction:column}.workspace-area{flex:1;min-height:0;display:flex;flex-direction:column;--hm-tab-strip-bg: var(--hm-shell-tab-strip-bg);--hm-tab-active-bg: var(--hm-surface-raised)}hm-workspace{flex:1;min-height:0}.panel-content{display:flex;flex-direction:column;height:100%;min-height:0}\n"], dependencies: [{ kind: "component", type: SpineNewsComponent, selector: "spine-news" }, { kind: "component", type: SpineDocsComponent, selector: "spine-docs" }, { kind: "component", type: HeaderComponent, selector: "spine-header" }, { kind: "component", type: SidebarComponent, selector: "spine-sidebar" }, { kind: "component", type: SpineConnectionBannerComponent, selector: "spine-connection-banner" }, { kind: "component", type: SpinePagePanelComponent, selector: "spine-page-panel", inputs: ["url", "isFocusedPanel", "tabId", "panelId"] }, { kind: "directive", type: PanelLocalLinkDirective, selector: "[appPanelLocalLink]", inputs: ["appPanelLocalLink"], outputs: ["localNavigate"] }, { kind: "component", type: WorkspaceComponent, selector: "hm-workspace", inputs: ["initialTabs", "initialActiveId", "keepAlive", "split", "maxColumns", "maxRows", "label", "providedStore"], outputs: ["stateChange", "newTabRequested"] }, { kind: "component", type: ToastStackComponent, selector: "hm-toast-stack", inputs: ["position", "expand", "visibleToasts"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: ModalComponent$1, selector: "spine-modal" }, { kind: "component", type: SidePanelComponent, selector: "spine-side-panel" }, { kind: "component", type: SpineInitialSetupComponent, selector: "spine-initial-setup" }, { kind: "component", type: ShortcutsCheatsheetComponent, selector: "spine-shortcuts-cheatsheet" }, { kind: "component", type: SettingsPanelComponent, selector: "spine-settings-panel" }, { kind: "component", type: SpineIdleWarningComponent, selector: "spine-idle-warning" }, { kind: "component", type: SkipLinkComponent, selector: "hm-skip-link", inputs: ["target", "label", "texts"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9318
+ `, isInline: true, styles: [":host{display:block}.shell{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--hm-surface);color:var(--hm-text)}.body{position:relative;display:flex;flex:1;min-height:0;overflow:hidden}.main{display:flex;flex:1;min-width:0;flex-direction:column}.workspace-area{flex:1;min-height:0;display:flex;flex-direction:column;--hm-tab-strip-bg: var(--hm-shell-tab-strip-bg);--hm-tab-active-bg: var(--hm-surface-raised)}hm-workspace{flex:1;min-height:0}.panel-content{display:flex;flex-direction:column;height:100%;min-height:0}\n"], dependencies: [{ kind: "component", type: SpineNewsComponent, selector: "spine-news" }, { kind: "component", type: SpineDocsComponent, selector: "spine-docs" }, { kind: "component", type: HeaderComponent, selector: "spine-header" }, { kind: "component", type: SidebarComponent, selector: "spine-sidebar" }, { kind: "component", type: SpineConnectionBannerComponent, selector: "spine-connection-banner" }, { kind: "component", type: SpinePagePanelComponent, selector: "spine-page-panel", inputs: ["url", "isFocusedPanel", "tabId", "panelId"] }, { kind: "directive", type: PanelLocalLinkDirective, selector: "[appPanelLocalLink]", inputs: ["appPanelLocalLink"], outputs: ["localNavigate"] }, { kind: "component", type: WorkspaceComponent, selector: "hm-workspace", inputs: ["initialTabs", "initialActiveId", "keepAlive", "split", "maxColumns", "maxRows", "label", "providedStore"], outputs: ["stateChange", "newTabRequested"] }, { kind: "component", type: ToastStackComponent, selector: "hm-toast-stack", inputs: ["position", "expand", "visibleToasts"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: ModalComponent$1, selector: "spine-modal" }, { kind: "component", type: SidePanelComponent, selector: "spine-side-panel" }, { kind: "component", type: SpineInitialSetupComponent, selector: "spine-initial-setup" }, { kind: "component", type: ShortcutsCheatsheetComponent, selector: "spine-shortcuts-cheatsheet" }, { kind: "component", type: SettingsPanelComponent, selector: "spine-settings-panel" }, { kind: "component", type: SpineIdleWarningComponent, selector: "spine-idle-warning" }, { kind: "component", type: SkipLinkComponent, selector: "hm-skip-link", inputs: ["target", "label", "texts"] }, { kind: "component", type: SpineAiAgentLauncherComponent, selector: "spine-ai-agent-launcher" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8367
9319
  }
8368
9320
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LayoutComponent, decorators: [{
8369
9321
  type: Component,
@@ -8385,6 +9337,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
8385
9337
  SettingsPanelComponent,
8386
9338
  SpineIdleWarningComponent,
8387
9339
  SkipLinkComponent,
9340
+ SpineAiAgentLauncherComponent,
8388
9341
  ], changeDetection: ChangeDetectionStrategy.OnPush, template: `
8389
9342
  <!-- "Pular para o conteúdo" (SpineFeatures.skipLink): primeira parada do Tab. -->
8390
9343
  @if (skipLink) {
@@ -8421,6 +9374,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
8421
9374
  <!-- Todos os avisos (respostas do servidor, avisos do app, configurações, auto-serviço…). Se o app também montar
8422
9375
  uma, só uma desenha (o serviço escolhe a pilha ativa): nunca em dobro. -->
8423
9376
  <hm-toast-stack />
9377
+ <!-- Agente de IA do app (SpineFeatures.aiAgent): botão flutuante no canto, que abre a conversa. -->
9378
+ <spine-ai-agent-launcher />
8424
9379
  <spine-side-panel />
8425
9380
  <spine-modal />
8426
9381
  <spine-shortcuts-cheatsheet></spine-shortcuts-cheatsheet>
@@ -8760,5 +9715,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
8760
9715
  * Generated bundle index. Do not edit.
8761
9716
  */
8762
9717
 
8763
- export { SpineConnectionBannerComponent as $, AiAgentComponent as A, SPINE_INBOX_SOURCES as B, SPINE_MARKDOWN_PURIFY_CONFIG as C, SPINE_PREFERENCES_SAVE_DELAY_MS as D, SPINE_RATING_COMMENT_MAX as E, SPINE_RATING_SCORE_MAX as F, SPINE_SETTINGS_ALIASES as G, HeaderComponent as H, SPINE_SETTINGS_KEYS as I, SPINE_SETTINGS_SECTIONS as J, SPINE_SUGGESTIONS_PAGE_ID as K, LayoutComponent as L, SPINE_SUGGESTION_LIMITS as M, NEW_SCREEN_VALUE as N, SPINE_SUGGESTION_PUBLIC_SORTS as O, SPINE_SUGGESTION_PUBLIC_STATUSES as P, SPINE_SUGGESTION_STATUS_ICONS as Q, SPINE_WORKSPACE_LIMITS as R, SpineSuggestionsService as S, SettingsPanelComponent as T, ShortcutService as U, ShortcutsCheatsheetComponent as V, SidebarComponent as W, SpineAccountPreferencesService as X, SpineAccountSecurityService as Y, SpineAppTokensService as Z, SpineBrandComponent as _, SpineInboxService as a, SpineDocsComponent as a0, SpineDocsService as a1, SpineIdleService as a2, SpineIdleWarningComponent as a3, SpineInboxButtonComponent as a4, SpineInitialSetupComponent as a5, SpineInitialSetupService as a6, SpineLogoutService as a7, SpineMarkdownComponent as a8, SpineMyGroupsService as a9, spinePathOf as aA, spineScoreBand as aB, spineStarFills as aC, spineSuggestionsEnabled as aD, spineTagColor as aE, stable as aF, SpineNewsComponent as aa, SpineNewsService as ab, SpineNoticesService as ac, SpinePageActionsDirective as ad, SpinePageActionsSlot as ae, SpineRatingStarsComponent as af, SpineRatingsService as ag, SpineSettingsService as ah, SpineStartPageService as ai, SpineUnsavedChangesService as aj, SpineWorkspaceNavService as ak, accountErrorCode as al, argosAccountApi as am, buildMcpConfig as an, effectiveIdleMinutes as ao, idleLimits as ap, isInternalPath as aq, provideSettingsSection as ar, provideSpineInboxSource as as, provideSpineSuggestionsPage as at, ratingTargetKey as au, registerSpineUnsavedChanges as av, resolveSettingsSection as aw, sanitizeSpineMarkdownHtml as ax, spineNormalizeUrl as ay, spineParamsOf as az, SpineSuggestionChatComponent as b, SPINE_SUGGESTION_STATUS_COLORS as c, SPINE_SUGGESTION_KIND_COLORS as d, SPINE_SUGGESTION_KIND_ICONS as e, SPINE_SUGGESTION_WAITING_COLORS as f, SPINE_SUGGESTION_VISIBLE_SORTS as g, SPINE_SUGGESTION_REASONS as h, SPINE_SUGGESTION_STATUSES as i, SPINE_SUGGESTION_KINDS as j, SPINE_SUGGESTION_WAITING as k, SPINE_TAB as l, SPINE_SUGGESTION_PAGE_PARAM as m, SPINE_SUGGESTION_TAB_PARAM as n, SPINE_SUGGESTION_TAB_CONVERSATION as o, screenName as p, screenModule as q, SPINE_SUGGESTIONS_PAGE_PATH as r, suggestionScreens as s, SpineSuggestionsDialogComponent as t, SpineTagRowComponent as u, AppLauncherComponent as v, SPINE_BRAND as w, SPINE_GLOBAL_PREFERENCE_KEYS as x, SPINE_IDLE_WARNING_SECONDS as y, SPINE_INBOX_POLL_MS as z };
8764
- //# sourceMappingURL=trismegisto-spine-layout-trismegisto-spine-layout-DiAfxIGP.mjs.map
9718
+ export { SpineAppTokensService as $, AppLauncherComponent as A, SPINE_INBOX_POLL_MS as B, SPINE_INBOX_SOURCES as C, SPINE_MARKDOWN_PURIFY_CONFIG as D, SPINE_PREFERENCES_SAVE_DELAY_MS as E, SPINE_RATING_COMMENT_MAX as F, SPINE_RATING_SCORE_MAX as G, HeaderComponent as H, SPINE_SETTINGS_ALIASES as I, SPINE_SETTINGS_KEYS as J, SPINE_SETTINGS_SECTIONS as K, LayoutComponent as L, McpConnectComponent as M, NEW_SCREEN_VALUE as N, SPINE_SUGGESTIONS_PAGE_ID as O, SPINE_SUGGESTION_LIMITS as P, SPINE_SUGGESTION_PUBLIC_SORTS as Q, SPINE_SUGGESTION_PUBLIC_STATUSES as R, SpineSuggestionsService as S, SPINE_SUGGESTION_STATUS_ICONS as T, SPINE_WORKSPACE_LIMITS as U, SettingsPanelComponent as V, ShortcutService as W, ShortcutsCheatsheetComponent as X, SidebarComponent as Y, SpineAccountPreferencesService as Z, SpineAccountSecurityService as _, SpineInboxService as a, SpineBrandComponent as a0, SpineConnectionBannerComponent as a1, SpineDocsComponent as a2, SpineDocsService as a3, SpineIdleService as a4, SpineIdleWarningComponent as a5, SpineInboxButtonComponent as a6, SpineInitialSetupComponent as a7, SpineInitialSetupService as a8, SpineLogoutService as a9, spineParamsOf as aA, spinePathOf as aB, spineScoreBand as aC, spineStarFills as aD, spineSuggestionsEnabled as aE, spineTagColor as aF, stable as aG, SpineMyGroupsService as aa, SpineNewsComponent as ab, SpineNewsService as ac, SpineNoticesService as ad, SpinePageActionsDirective as ae, SpinePageActionsSlot as af, SpineRatingStarsComponent as ag, SpineRatingsService as ah, SpineSettingsService as ai, SpineStartPageService as aj, SpineUnsavedChangesService as ak, SpineWorkspaceNavService as al, accountErrorCode as am, argosAccountApi as an, buildMcpConfig as ao, effectiveIdleMinutes as ap, idleLimits as aq, isInternalPath as ar, provideSettingsSection as as, provideSpineInboxSource as at, provideSpineSuggestionsPage as au, ratingTargetKey as av, registerSpineUnsavedChanges as aw, resolveSettingsSection as ax, sanitizeSpineMarkdownHtml as ay, spineNormalizeUrl as az, SpineSuggestionChatComponent as b, SPINE_SUGGESTION_STATUS_COLORS as c, SPINE_SUGGESTION_KIND_COLORS as d, SPINE_SUGGESTION_KIND_ICONS as e, SPINE_SUGGESTION_WAITING_COLORS as f, SPINE_SUGGESTION_VISIBLE_SORTS as g, SPINE_SUGGESTION_REASONS as h, SPINE_SUGGESTION_STATUSES as i, SPINE_SUGGESTION_KINDS as j, SPINE_SUGGESTION_WAITING as k, SPINE_TAB as l, SPINE_SUGGESTION_PAGE_PARAM as m, SPINE_SUGGESTION_TAB_PARAM as n, SPINE_SUGGESTION_TAB_CONVERSATION as o, screenName as p, screenModule as q, SPINE_SUGGESTIONS_PAGE_PATH as r, suggestionScreens as s, SpineSuggestionsDialogComponent as t, SpineTagRowComponent as u, SpineAiAgentService as v, SpineMarkdownComponent as w, SPINE_BRAND as x, SPINE_GLOBAL_PREFERENCE_KEYS as y, SPINE_IDLE_WARNING_SECONDS as z };
9719
+ //# sourceMappingURL=trismegisto-spine-layout-trismegisto-spine-layout-B47sMo_4.mjs.map