@trismegisto/spine 3.3.6 → 3.3.7

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.
@@ -1915,19 +1915,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1915
1915
  const TOKEN_HEADER = 'x-access-token';
1916
1916
  const WS_PROTOCOL = 'graphql-transport-ws';
1917
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 }
1918
+ // Nenhuma operação leva o agente: o gateway o descobre pelo client do token.
1919
+ const EVENTS_QUERY = `subscription ($conversationId: ID!) {
1920
+ agentEvents(conversationId: $conversationId) { type text tool title ok detail message }
1920
1921
  }`;
1921
- const SEND_MUTATION = `mutation ($agentId: ID!, $input: SendAgentMessageInput!) {
1922
- sendAgentMessage(agentId: $agentId, input: $input) { status message }
1922
+ const SEND_MUTATION = `mutation ($input: SendAgentMessageInput!) {
1923
+ sendAgentMessage(input: $input) { status message }
1923
1924
  }`;
1924
- const RATE_MUTATION = `mutation ($agentId: ID!, $input: RateAgentMessageInput!) {
1925
- rateAgentMessage(agentId: $agentId, input: $input) { status message }
1925
+ const RATE_MUTATION = `mutation ($input: RateAgentMessageInput!) {
1926
+ rateAgentMessage(input: $input) { status message }
1926
1927
  }`;
1927
- const CONVERSATION_QUERY = `query ($agentId: ID!, $conversationId: ID!) {
1928
- agentConversation(agentId: $agentId, conversationId: $conversationId) { status data { role text steps { tool title ok detail } } }
1928
+ const CONVERSATION_QUERY = `query ($conversationId: ID!) {
1929
+ agentConversation(conversationId: $conversationId) { status data { role text steps { tool title ok detail } } }
1929
1930
  }`;
1930
- const AGENTS_QUERY = `query { agents { status data { id suggestions } } }`;
1931
+ const AGENT_QUERY = `query { agent { status data { suggestions } } }`;
1931
1932
  /**
1932
1933
  * Assina os eventos de uma conversa e devolve a função que encerra a assinatura. Usa o WebSocket do navegador, com o
1933
1934
  * protocolo do graphql-ws: o shell também atende apps sem GraphQL, que não têm o pacote `graphql-ws` instalado.
@@ -1976,7 +1977,7 @@ async function sendAgentMessage(url, token, variables, text) {
1976
1977
  headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
1977
1978
  body: JSON.stringify({
1978
1979
  query: SEND_MUTATION,
1979
- variables: { agentId: variables.agentId, input: { conversationId: variables.conversationId, text } },
1980
+ variables: { input: { conversationId: variables.conversationId, text } },
1980
1981
  }),
1981
1982
  });
1982
1983
  const body = await response.json();
@@ -1989,22 +1990,21 @@ async function rateAgentMessage(url, token, variables, messageIndex, rating, cor
1989
1990
  headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
1990
1991
  body: JSON.stringify({
1991
1992
  query: RATE_MUTATION,
1992
- variables: { agentId: variables.agentId, input: { conversationId: variables.conversationId, messageIndex, rating, correction } },
1993
+ variables: { input: { conversationId: variables.conversationId, messageIndex, rating, correction } },
1993
1994
  }),
1994
1995
  });
1995
1996
  const body = await response.json();
1996
1997
  return body?.data?.rateAgentMessage ?? { status: response.status >= 400 ? response.status : 500, message: '' };
1997
1998
  }
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) {
1999
+ /** Perguntas prontas que o agente oferece. */
2000
+ async function fetchAgentSuggestions(url, token) {
2000
2001
  const response = await fetch(url, {
2001
2002
  method: 'POST',
2002
2003
  headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
2003
- body: JSON.stringify({ query: AGENTS_QUERY }),
2004
+ body: JSON.stringify({ query: AGENT_QUERY }),
2004
2005
  });
2005
2006
  const body = await response.json();
2006
- const agents = body?.data?.agents?.data ?? [];
2007
- return agents.find((agent) => agent.id === agentId)?.suggestions ?? [];
2007
+ return body?.data?.agent?.data?.suggestions ?? [];
2008
2008
  }
2009
2009
  /** As perguntas e respostas de uma conversa que já existe; vazia se ela expirou. */
2010
2010
  async function fetchAgentConversation(url, token, variables) {
@@ -2060,7 +2060,8 @@ function writeStored(key, value) {
2060
2060
  */
2061
2061
  class SpineAiAgentService {
2062
2062
  constructor() {
2063
- this.config = inject(SPINE_CONFIG, { optional: true })?.features?.aiAgent ?? null;
2063
+ this.spine = inject(SPINE_CONFIG, { optional: true });
2064
+ this.config = this.spine?.features?.aiAgent ?? null;
2064
2065
  this.auth = inject(AuthorizationService);
2065
2066
  this.text = inject(SPINE_MESSAGES).aiAgentChat;
2066
2067
  this.enabled = !!this.config;
@@ -2089,7 +2090,8 @@ class SpineAiAgentService {
2089
2090
  this.position = signal(readPlacement().position ?? null, ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
2090
2091
  /** A pessoa pediu o painel agora: a conversa recebe o foco. Ao recarregar a tela, não. */
2091
2092
  this.focusOnOpen = false;
2092
- this.storageKey = `${STORAGE_PREFIX}${this.config?.agent ?? ''}`;
2093
+ // Por app: vários apps podem morar na mesma origem, e o localStorage é dela.
2094
+ this.storageKey = `${STORAGE_PREFIX}${this.spine?.clientId ?? ''}`;
2093
2095
  this.storedConversation = readStored(this.storageKey);
2094
2096
  this.conversationId = this.storedConversation ?? this.newConversationId();
2095
2097
  this.restored = !this.storedConversation;
@@ -2147,7 +2149,7 @@ class SpineAiAgentService {
2147
2149
  if (!this.config || !token || this.suggestionsLoaded)
2148
2150
  return;
2149
2151
  this.suggestionsLoaded = true;
2150
- fetchAgentSuggestions(this.config.http, token, this.config.agent).then((suggestions) => this.suggestions.set(suggestions), () => { this.suggestionsLoaded = false; });
2152
+ fetchAgentSuggestions(this.config.http, token).then((suggestions) => this.suggestions.set(suggestions), () => { this.suggestionsLoaded = false; });
2151
2153
  }
2152
2154
  /** Traz de volta a conversa guardada, uma vez. Se falhar, ou se ela expirou, o chat só começa vazio. */
2153
2155
  restoreConversation() {
@@ -2156,7 +2158,7 @@ class SpineAiAgentService {
2156
2158
  return;
2157
2159
  this.restored = true;
2158
2160
  const conversationId = this.conversationId;
2159
- fetchAgentConversation(this.config.http, token, { agentId: this.config.agent, conversationId }).then((messages) => {
2161
+ fetchAgentConversation(this.config.http, token, { conversationId }).then((messages) => {
2160
2162
  // A pessoa pode ter começado a escrever antes de a resposta chegar: o que está na tela vence.
2161
2163
  if (this.entries().length || conversationId !== this.conversationId)
2162
2164
  return;
@@ -2182,7 +2184,7 @@ class SpineAiAgentService {
2182
2184
  const index = saved.findIndex((entry) => entry.id === entryId);
2183
2185
  if (!this.config || !token || index < 1)
2184
2186
  return false;
2185
- const variables = { agentId: this.config.agent, conversationId: this.conversationId };
2187
+ const variables = { conversationId: this.conversationId };
2186
2188
  try {
2187
2189
  const { status } = await rateAgentMessage(this.config.http, token, variables, index, rating, correction);
2188
2190
  if (status >= 400)
@@ -2194,6 +2196,46 @@ class SpineAiAgentService {
2194
2196
  this.update(entryId, (entry) => ({ ...entry, rating }));
2195
2197
  return true;
2196
2198
  }
2199
+ /**
2200
+ * Baixa a conversa da tela em PDF, gerado aqui no navegador: nada sai para o backend. O gerador vem só no clique, para
2201
+ * não pesar em quem nunca baixa. Devolve `false` se não houver o que baixar ou se a geração falhar.
2202
+ */
2203
+ async downloadTranscript() {
2204
+ const entries = this.entries().filter((entry) => entry.text.trim() || entry.steps.length);
2205
+ if (!entries.length)
2206
+ return false;
2207
+ try {
2208
+ const { buildTranscriptPdf, transcriptFileName } = await import('./trismegisto-spine-layout-ai-agent-transcript-BNcgbrV_.mjs');
2209
+ const now = new Date();
2210
+ const person = this.auth.profile?.name;
2211
+ const pdf = buildTranscriptPdf({
2212
+ title: this.title,
2213
+ subtitle: [this.spine?.appName, person, now.toLocaleString()].filter(Boolean).join(' · '),
2214
+ personLabel: person || this.text.you,
2215
+ agentLabel: this.title,
2216
+ queriesLabel: this.text.transcriptQueries,
2217
+ failedLabel: this.text.transcriptStepFailed,
2218
+ pageLabel: this.text.transcriptPage,
2219
+ entries: entries.map((entry) => ({
2220
+ role: entry.role,
2221
+ text: entry.text,
2222
+ error: entry.error,
2223
+ steps: entry.steps.map((step) => ({ title: step.title, detail: step.detail, failed: step.status === 'error' })),
2224
+ })),
2225
+ });
2226
+ const url = URL.createObjectURL(new Blob([pdf.slice().buffer], { type: 'application/pdf' }));
2227
+ const link = document.createElement('a');
2228
+ link.href = url;
2229
+ link.download = transcriptFileName(this.title, this.text.transcriptFile, now);
2230
+ link.click();
2231
+ // O navegador só lê o endereço depois do clique: revogar na hora cancelaria o download em alguns deles.
2232
+ setTimeout(() => URL.revokeObjectURL(url), 10_000);
2233
+ return true;
2234
+ }
2235
+ catch {
2236
+ return false;
2237
+ }
2238
+ }
2197
2239
  /** Repete a última pergunta depois de um erro. Resposta que falhou não entra no histórico do backend. */
2198
2240
  retry() {
2199
2241
  const entries = this.entries();
@@ -2216,7 +2258,7 @@ class SpineAiAgentService {
2216
2258
  return;
2217
2259
  }
2218
2260
  this.running.set(true);
2219
- this.listen(answerId, { agentId: this.config.agent, conversationId: this.conversationId }, text);
2261
+ this.listen(answerId, { conversationId: this.conversationId }, text);
2220
2262
  }
2221
2263
  listen(answerId, variables, question) {
2222
2264
  const token = this.auth.getToken();
@@ -7558,7 +7600,7 @@ function registerPage() {
7558
7600
  route_redirect: SPINE_SUGGESTIONS_PAGE_PATH,
7559
7601
  description: text.description,
7560
7602
  },
7561
- load: () => import('./trismegisto-spine-layout-suggestions-page.component-Bh3VGlOR.mjs').then((m) => m.SpineSuggestionsPageComponent),
7603
+ load: () => import('./trismegisto-spine-layout-suggestions-page.component-BEGFNlvw.mjs').then((m) => m.SpineSuggestionsPageComponent),
7562
7604
  });
7563
7605
  });
7564
7606
  }
@@ -8703,6 +8745,7 @@ class SpineAiAgentLauncherComponent {
8703
8745
  constructor() {
8704
8746
  this.agent = inject(SpineAiAgentService);
8705
8747
  this.text = inject(SPINE_MESSAGES).aiAgentChat;
8748
+ this.toast = inject(HermesToastService);
8706
8749
  this.panelId = PANEL_ID;
8707
8750
  this.titleId = TITLE_ID;
8708
8751
  /** Tamanho do painel: continua como a pessoa deixou ao fechar e abrir. */
@@ -8761,7 +8804,7 @@ class SpineAiAgentLauncherComponent {
8761
8804
  const focus = this.agent.focusOnOpen;
8762
8805
  this.agent.focusOnOpen = false;
8763
8806
  if (!this.chat()) {
8764
- const { SpineAiAgentChatComponent } = await import('./trismegisto-spine-layout-ai-agent-chat.component-C5JljPYD.mjs');
8807
+ const { SpineAiAgentChatComponent } = await import('./trismegisto-spine-layout-ai-agent-chat.component-LlxtxNA5.mjs');
8765
8808
  this.chat.set(SpineAiAgentChatComponent);
8766
8809
  }
8767
8810
  this.agent.loadSuggestions();
@@ -8797,6 +8840,10 @@ class SpineAiAgentLauncherComponent {
8797
8840
  this.agent.dock();
8798
8841
  afterNextRender(() => this.focusEntry(), { injector: this.injector });
8799
8842
  }
8843
+ async downloadTranscript() {
8844
+ if (!(await this.agent.downloadTranscript()))
8845
+ this.toast.error(this.text.transcriptDownloadError);
8846
+ }
8800
8847
  undock() {
8801
8848
  this.agent.undock();
8802
8849
  afterNextRender(() => this.focusEntry(), { injector: this.injector });
@@ -8870,6 +8917,14 @@ class SpineAiAgentLauncherComponent {
8870
8917
  [disabled]="agent.running() || !agent.entries().length"
8871
8918
  (clicked)="agent.reset()"
8872
8919
  />
8920
+ <hm-icon-button
8921
+ emphasis="subtle"
8922
+ size="small"
8923
+ icon="download"
8924
+ [label]="text.transcriptDownload"
8925
+ [disabled]="agent.running() || !agent.entries().length"
8926
+ (clicked)="downloadTranscript()"
8927
+ />
8873
8928
  <hm-icon-button
8874
8929
  emphasis="subtle"
8875
8930
  size="small"
@@ -8977,6 +9032,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
8977
9032
  [disabled]="agent.running() || !agent.entries().length"
8978
9033
  (clicked)="agent.reset()"
8979
9034
  />
9035
+ <hm-icon-button
9036
+ emphasis="subtle"
9037
+ size="small"
9038
+ icon="download"
9039
+ [label]="text.transcriptDownload"
9040
+ [disabled]="agent.running() || !agent.entries().length"
9041
+ (clicked)="downloadTranscript()"
9042
+ />
8980
9043
  <hm-icon-button
8981
9044
  emphasis="subtle"
8982
9045
  size="small"
@@ -9716,4 +9779,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
9716
9779
  */
9717
9780
 
9718
9781
  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
9782
+ //# sourceMappingURL=trismegisto-spine-layout-trismegisto-spine-layout-D3vetDC_.mjs.map