@trismegisto/spine 3.3.6 → 3.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/fesm2022/{trismegisto-spine-layout-ai-agent-chat.component-C5JljPYD.mjs → trismegisto-spine-layout-ai-agent-chat.component-Brh0_LS9.mjs} +4 -4
- package/fesm2022/trismegisto-spine-layout-ai-agent-chat.component-Brh0_LS9.mjs.map +1 -0
- package/fesm2022/trismegisto-spine-layout-ai-agent-transcript-BNcgbrV_.mjs +438 -0
- package/fesm2022/trismegisto-spine-layout-ai-agent-transcript-BNcgbrV_.mjs.map +1 -0
- package/fesm2022/{trismegisto-spine-layout-suggestions-page.component-Bh3VGlOR.mjs → trismegisto-spine-layout-suggestions-page.component-Cj4KgMj3.mjs} +2 -2
- package/fesm2022/{trismegisto-spine-layout-suggestions-page.component-Bh3VGlOR.mjs.map → trismegisto-spine-layout-suggestions-page.component-Cj4KgMj3.mjs.map} +1 -1
- package/fesm2022/{trismegisto-spine-layout-trismegisto-spine-layout-B47sMo_4.mjs → trismegisto-spine-layout-trismegisto-spine-layout-UM7CC7Sj.mjs} +222 -46
- package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-UM7CC7Sj.mjs.map +1 -0
- package/fesm2022/trismegisto-spine-layout.mjs +1 -1
- package/fesm2022/trismegisto-spine.mjs +18 -0
- package/fesm2022/trismegisto-spine.mjs.map +1 -1
- package/package.json +6 -6
- package/types/trismegisto-spine.d.ts +8 -2
- package/types/trismegisto-spine.d.ts.map +1 -1
- package/fesm2022/trismegisto-spine-layout-ai-agent-chat.component-C5JljPYD.mjs.map +0 -1
- package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-B47sMo_4.mjs.map +0 -1
|
@@ -1915,19 +1915,23 @@ 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
|
-
|
|
1919
|
-
|
|
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 ($
|
|
1922
|
-
sendAgentMessage(
|
|
1922
|
+
const SEND_MUTATION = `mutation ($input: SendAgentMessageInput!) {
|
|
1923
|
+
sendAgentMessage(input: $input) { status message }
|
|
1923
1924
|
}`;
|
|
1924
|
-
const RATE_MUTATION = `mutation ($
|
|
1925
|
-
rateAgentMessage(
|
|
1925
|
+
const RATE_MUTATION = `mutation ($input: RateAgentMessageInput!) {
|
|
1926
|
+
rateAgentMessage(input: $input) { status message }
|
|
1926
1927
|
}`;
|
|
1927
|
-
const CONVERSATION_QUERY = `query ($
|
|
1928
|
-
agentConversation(
|
|
1928
|
+
const CONVERSATION_QUERY = `query ($conversationId: ID!) {
|
|
1929
|
+
agentConversation(conversationId: $conversationId) { status data { role text steps { tool title ok detail } } }
|
|
1930
|
+
}`;
|
|
1931
|
+
const AGENT_QUERY = `query { agent { status data { title suggestions greeting { title text } } } }`;
|
|
1932
|
+
const REFRESH_MUTATION = `mutation ($conversationId: ID!) {
|
|
1933
|
+
refreshAgentCredential(conversationId: $conversationId) { status message }
|
|
1929
1934
|
}`;
|
|
1930
|
-
const AGENTS_QUERY = `query { agents { status data { id suggestions } } }`;
|
|
1931
1935
|
/**
|
|
1932
1936
|
* Assina os eventos de uma conversa e devolve a função que encerra a assinatura. Usa o WebSocket do navegador, com o
|
|
1933
1937
|
* protocolo do graphql-ws: o shell também atende apps sem GraphQL, que não têm o pacote `graphql-ws` instalado.
|
|
@@ -1976,7 +1980,7 @@ async function sendAgentMessage(url, token, variables, text) {
|
|
|
1976
1980
|
headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
|
|
1977
1981
|
body: JSON.stringify({
|
|
1978
1982
|
query: SEND_MUTATION,
|
|
1979
|
-
variables: {
|
|
1983
|
+
variables: { input: { conversationId: variables.conversationId, text } },
|
|
1980
1984
|
}),
|
|
1981
1985
|
});
|
|
1982
1986
|
const body = await response.json();
|
|
@@ -1989,22 +1993,47 @@ async function rateAgentMessage(url, token, variables, messageIndex, rating, cor
|
|
|
1989
1993
|
headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
|
|
1990
1994
|
body: JSON.stringify({
|
|
1991
1995
|
query: RATE_MUTATION,
|
|
1992
|
-
variables: {
|
|
1996
|
+
variables: { input: { conversationId: variables.conversationId, messageIndex, rating, correction } },
|
|
1993
1997
|
}),
|
|
1994
1998
|
});
|
|
1995
1999
|
const body = await response.json();
|
|
1996
2000
|
return body?.data?.rateAgentMessage ?? { status: response.status >= 400 ? response.status : 500, message: '' };
|
|
1997
2001
|
}
|
|
1998
|
-
/**
|
|
1999
|
-
|
|
2002
|
+
/** Gateway fora do ar ou com erro: vale tentar de novo. Nunca chega à tela. */
|
|
2003
|
+
class GatewayUnavailable extends Error {
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* O agente desta pessoa, com as perguntas prontas dele. `null` quando o gateway responde que ela não tem agente: o app não
|
|
2007
|
+
* tem um, ou a pessoa está fora dos grupos dele, ou o token não é aceito. Falha de rede ou do gateway sobe como exceção,
|
|
2008
|
+
* para quem chama tentar de novo.
|
|
2009
|
+
*/
|
|
2010
|
+
async function fetchAgent(url, token) {
|
|
2011
|
+
const response = await fetch(url, {
|
|
2012
|
+
method: 'POST',
|
|
2013
|
+
headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
|
|
2014
|
+
body: JSON.stringify({ query: AGENT_QUERY }),
|
|
2015
|
+
});
|
|
2016
|
+
if (response.status >= 500)
|
|
2017
|
+
throw new GatewayUnavailable();
|
|
2018
|
+
const body = await response.json();
|
|
2019
|
+
const refused = (body?.errors ?? []).some((error) => [401, 403, 'UNAUTHENTICATED', 'FORBIDDEN'].includes(error?.extensions?.code));
|
|
2020
|
+
const envelope = body?.data?.agent;
|
|
2021
|
+
if (refused || !envelope || (envelope.status ?? 200) >= 400 || !envelope.data) {
|
|
2022
|
+
if (!refused && !envelope && body?.errors?.length)
|
|
2023
|
+
throw new GatewayUnavailable();
|
|
2024
|
+
return null;
|
|
2025
|
+
}
|
|
2026
|
+
return { title: envelope.data.title, suggestions: envelope.data.suggestions ?? [], greeting: envelope.data.greeting };
|
|
2027
|
+
}
|
|
2028
|
+
/** Leva o token renovado à resposta em andamento. Falha aqui não muda nada na tela: a resposta conclui com o que tiver. */
|
|
2029
|
+
async function refreshAgentCredential(url, token, variables) {
|
|
2000
2030
|
const response = await fetch(url, {
|
|
2001
2031
|
method: 'POST',
|
|
2002
2032
|
headers: { 'content-type': 'application/json', [TOKEN_HEADER]: token },
|
|
2003
|
-
body: JSON.stringify({ query:
|
|
2033
|
+
body: JSON.stringify({ query: REFRESH_MUTATION, variables }),
|
|
2004
2034
|
});
|
|
2005
2035
|
const body = await response.json();
|
|
2006
|
-
|
|
2007
|
-
return agents.find((agent) => agent.id === agentId)?.suggestions ?? [];
|
|
2036
|
+
return body?.data?.refreshAgentCredential?.status ?? response.status;
|
|
2008
2037
|
}
|
|
2009
2038
|
/** As perguntas e respostas de uma conversa que já existe; vazia se ela expirou. */
|
|
2010
2039
|
async function fetchAgentConversation(url, token, variables) {
|
|
@@ -2020,6 +2049,12 @@ async function fetchAgentConversation(url, token, variables) {
|
|
|
2020
2049
|
// Teto para o agente dar sinal de vida; acima do limite de uma execução no backend.
|
|
2021
2050
|
const SILENCE_LIMIT_MS = 200_000;
|
|
2022
2051
|
const STORAGE_PREFIX = 'spine-ai-agent:conversation:';
|
|
2052
|
+
// Sem token ainda ou com o gateway fora: novas tentativas em 2, 4 e 6 s, e então desiste até a próxima tela.
|
|
2053
|
+
const AVAILABILITY_RETRIES = 3;
|
|
2054
|
+
const AVAILABILITY_RETRY_MS = 2_000;
|
|
2055
|
+
// De quanto em quanto tempo confere se o login trocou o token durante uma resposta. O agente começa a procurar o novo um
|
|
2056
|
+
// minuto antes do antigo vencer, então há folga de sobra.
|
|
2057
|
+
const CREDENTIAL_CHECK_MS = 10_000;
|
|
2023
2058
|
const PLACEMENT_KEY = 'spine-ai-agent:placement';
|
|
2024
2059
|
// 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
2060
|
const RECONNECT_ATTEMPTS = 5;
|
|
@@ -2060,11 +2095,18 @@ function writeStored(key, value) {
|
|
|
2060
2095
|
*/
|
|
2061
2096
|
class SpineAiAgentService {
|
|
2062
2097
|
constructor() {
|
|
2063
|
-
this.
|
|
2098
|
+
this.spine = inject(SPINE_CONFIG, { optional: true });
|
|
2099
|
+
this.config = this.spine?.features?.aiAgent ?? null;
|
|
2064
2100
|
this.auth = inject(AuthorizationService);
|
|
2065
2101
|
this.text = inject(SPINE_MESSAGES).aiAgentChat;
|
|
2066
2102
|
this.enabled = !!this.config;
|
|
2067
|
-
|
|
2103
|
+
/**
|
|
2104
|
+
* O gateway confirmou que esta pessoa tem um agente: só então o balão e o ícone do cabeçalho aparecem. App sem agente, pessoa
|
|
2105
|
+
* fora dos grupos dele ou gateway fora do ar: nada aparece, em vez de um chat que só falha.
|
|
2106
|
+
*/
|
|
2107
|
+
this.available = signal(false, ...(ngDevMode ? [{ debugName: "available" }] : /* istanbul ignore next */ []));
|
|
2108
|
+
/** Nome e saudação: os da definição do agente, que o gateway devolve; sem eles, os do app; sem estes, os padrões. */
|
|
2109
|
+
this.title = signal(this.config?.title || this.text.title, ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
2068
2110
|
this.theme = inject(ThemeService);
|
|
2069
2111
|
this.avatarSources = resolveSpineImage(this.config?.avatar, SPINE_DEFAULT_AGENT_AVATAR);
|
|
2070
2112
|
this.thinkingSources = resolveSpineImage(this.config?.avatarThinking, null);
|
|
@@ -2075,8 +2117,8 @@ class SpineAiAgentService {
|
|
|
2075
2117
|
this.avatar = computed(() => this.pickImage(this.avatarSources), ...(ngDevMode ? [{ debugName: "avatar" }] : /* istanbul ignore next */ []));
|
|
2076
2118
|
/** A figura do agente, para o tema atual. */
|
|
2077
2119
|
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;
|
|
2120
|
+
this.greetingTitle = signal(this.config?.greeting?.title || this.text.greetingTitle, ...(ngDevMode ? [{ debugName: "greetingTitle" }] : /* istanbul ignore next */ []));
|
|
2121
|
+
this.greetingText = signal(this.config?.greeting?.text || this.text.greetingText, ...(ngDevMode ? [{ debugName: "greetingText" }] : /* istanbul ignore next */ []));
|
|
2080
2122
|
this.entries = signal([], ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
2081
2123
|
this.running = signal(false, ...(ngDevMode ? [{ debugName: "running" }] : /* istanbul ignore next */ []));
|
|
2082
2124
|
/** Perguntas prontas que o agente oferece, mostradas na conversa vazia. */
|
|
@@ -2089,7 +2131,8 @@ class SpineAiAgentService {
|
|
|
2089
2131
|
this.position = signal(readPlacement().position ?? null, ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
2090
2132
|
/** A pessoa pediu o painel agora: a conversa recebe o foco. Ao recarregar a tela, não. */
|
|
2091
2133
|
this.focusOnOpen = false;
|
|
2092
|
-
|
|
2134
|
+
// Por app: vários apps podem morar na mesma origem, e o localStorage é dela.
|
|
2135
|
+
this.storageKey = `${STORAGE_PREFIX}${this.spine?.clientId ?? ''}`;
|
|
2093
2136
|
this.storedConversation = readStored(this.storageKey);
|
|
2094
2137
|
this.conversationId = this.storedConversation ?? this.newConversationId();
|
|
2095
2138
|
this.restored = !this.storedConversation;
|
|
@@ -2105,6 +2148,10 @@ class SpineAiAgentService {
|
|
|
2105
2148
|
/** Mensagens que o backend já tinha guardado antes desta pergunta, para reconhecer a resposta nova. */
|
|
2106
2149
|
this.savedBefore = 0;
|
|
2107
2150
|
this.suggestionsLoaded = false;
|
|
2151
|
+
this.availabilityTries = 0;
|
|
2152
|
+
/** O token que a resposta em andamento tem; quando o login troca o da pessoa, o novo é mandado a ela. */
|
|
2153
|
+
this.credentialSent = null;
|
|
2154
|
+
this.credentialTimer = null;
|
|
2108
2155
|
}
|
|
2109
2156
|
/** Como no logo do header: no escuro vale `dark`, e se ele falhar volta para `light`. */
|
|
2110
2157
|
pickImage(sources) {
|
|
@@ -2141,13 +2188,45 @@ class SpineAiAgentService {
|
|
|
2141
2188
|
this.restored = true;
|
|
2142
2189
|
this.send(question);
|
|
2143
2190
|
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Pergunta ao gateway se esta pessoa tem um agente, e já traz as sugestões. A resposta "não tem" é definitiva nesta tela;
|
|
2193
|
+
* sem token ainda, ou com o gateway fora, tenta de novo algumas vezes, cada vez mais devagar.
|
|
2194
|
+
*/
|
|
2195
|
+
checkAvailability() {
|
|
2196
|
+
if (!this.config || this.available())
|
|
2197
|
+
return;
|
|
2198
|
+
const token = this.auth.getToken();
|
|
2199
|
+
if (!token) {
|
|
2200
|
+
this.retryAvailability();
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
fetchAgent(this.config.http, token).then((agent) => {
|
|
2204
|
+
if (!agent)
|
|
2205
|
+
return;
|
|
2206
|
+
if (agent.title)
|
|
2207
|
+
this.title.set(agent.title);
|
|
2208
|
+
if (agent.greeting?.title)
|
|
2209
|
+
this.greetingTitle.set(agent.greeting.title);
|
|
2210
|
+
if (agent.greeting?.text)
|
|
2211
|
+
this.greetingText.set(agent.greeting.text);
|
|
2212
|
+
this.suggestions.set(agent.suggestions);
|
|
2213
|
+
this.suggestionsLoaded = true;
|
|
2214
|
+
this.available.set(true);
|
|
2215
|
+
}, () => this.retryAvailability());
|
|
2216
|
+
}
|
|
2217
|
+
retryAvailability() {
|
|
2218
|
+
if (this.availabilityTries >= AVAILABILITY_RETRIES)
|
|
2219
|
+
return;
|
|
2220
|
+
this.availabilityTries += 1;
|
|
2221
|
+
setTimeout(() => this.checkAvailability(), AVAILABILITY_RETRY_MS * this.availabilityTries);
|
|
2222
|
+
}
|
|
2144
2223
|
/** Busca as sugestões uma vez. Sem elas o chat funciona igual, então a falha não aparece para a pessoa. */
|
|
2145
2224
|
loadSuggestions() {
|
|
2146
2225
|
const token = this.auth.getToken();
|
|
2147
2226
|
if (!this.config || !token || this.suggestionsLoaded)
|
|
2148
2227
|
return;
|
|
2149
2228
|
this.suggestionsLoaded = true;
|
|
2150
|
-
|
|
2229
|
+
fetchAgent(this.config.http, token).then((agent) => this.suggestions.set(agent?.suggestions ?? []), () => { this.suggestionsLoaded = false; });
|
|
2151
2230
|
}
|
|
2152
2231
|
/** Traz de volta a conversa guardada, uma vez. Se falhar, ou se ela expirou, o chat só começa vazio. */
|
|
2153
2232
|
restoreConversation() {
|
|
@@ -2156,7 +2235,7 @@ class SpineAiAgentService {
|
|
|
2156
2235
|
return;
|
|
2157
2236
|
this.restored = true;
|
|
2158
2237
|
const conversationId = this.conversationId;
|
|
2159
|
-
fetchAgentConversation(this.config.http, token, {
|
|
2238
|
+
fetchAgentConversation(this.config.http, token, { conversationId }).then((messages) => {
|
|
2160
2239
|
// A pessoa pode ter começado a escrever antes de a resposta chegar: o que está na tela vence.
|
|
2161
2240
|
if (this.entries().length || conversationId !== this.conversationId)
|
|
2162
2241
|
return;
|
|
@@ -2182,7 +2261,7 @@ class SpineAiAgentService {
|
|
|
2182
2261
|
const index = saved.findIndex((entry) => entry.id === entryId);
|
|
2183
2262
|
if (!this.config || !token || index < 1)
|
|
2184
2263
|
return false;
|
|
2185
|
-
const variables = {
|
|
2264
|
+
const variables = { conversationId: this.conversationId };
|
|
2186
2265
|
try {
|
|
2187
2266
|
const { status } = await rateAgentMessage(this.config.http, token, variables, index, rating, correction);
|
|
2188
2267
|
if (status >= 400)
|
|
@@ -2194,6 +2273,46 @@ class SpineAiAgentService {
|
|
|
2194
2273
|
this.update(entryId, (entry) => ({ ...entry, rating }));
|
|
2195
2274
|
return true;
|
|
2196
2275
|
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Baixa a conversa da tela em PDF, gerado aqui no navegador: nada sai para o backend. O gerador vem só no clique, para
|
|
2278
|
+
* não pesar em quem nunca baixa. Devolve `false` se não houver o que baixar ou se a geração falhar.
|
|
2279
|
+
*/
|
|
2280
|
+
async downloadTranscript() {
|
|
2281
|
+
const entries = this.entries().filter((entry) => entry.text.trim() || entry.steps.length);
|
|
2282
|
+
if (!entries.length)
|
|
2283
|
+
return false;
|
|
2284
|
+
try {
|
|
2285
|
+
const { buildTranscriptPdf, transcriptFileName } = await import('./trismegisto-spine-layout-ai-agent-transcript-BNcgbrV_.mjs');
|
|
2286
|
+
const now = new Date();
|
|
2287
|
+
const person = this.auth.profile?.name;
|
|
2288
|
+
const pdf = buildTranscriptPdf({
|
|
2289
|
+
title: this.title(),
|
|
2290
|
+
subtitle: [this.spine?.appName, person, now.toLocaleString()].filter(Boolean).join(' · '),
|
|
2291
|
+
personLabel: person || this.text.you,
|
|
2292
|
+
agentLabel: this.title(),
|
|
2293
|
+
queriesLabel: this.text.transcriptQueries,
|
|
2294
|
+
failedLabel: this.text.transcriptStepFailed,
|
|
2295
|
+
pageLabel: this.text.transcriptPage,
|
|
2296
|
+
entries: entries.map((entry) => ({
|
|
2297
|
+
role: entry.role,
|
|
2298
|
+
text: entry.text,
|
|
2299
|
+
error: entry.error,
|
|
2300
|
+
steps: entry.steps.map((step) => ({ title: step.title, detail: step.detail, failed: step.status === 'error' })),
|
|
2301
|
+
})),
|
|
2302
|
+
});
|
|
2303
|
+
const url = URL.createObjectURL(new Blob([pdf.slice().buffer], { type: 'application/pdf' }));
|
|
2304
|
+
const link = document.createElement('a');
|
|
2305
|
+
link.href = url;
|
|
2306
|
+
link.download = transcriptFileName(this.title(), this.text.transcriptFile, now);
|
|
2307
|
+
link.click();
|
|
2308
|
+
// O navegador só lê o endereço depois do clique: revogar na hora cancelaria o download em alguns deles.
|
|
2309
|
+
setTimeout(() => URL.revokeObjectURL(url), 10_000);
|
|
2310
|
+
return true;
|
|
2311
|
+
}
|
|
2312
|
+
catch {
|
|
2313
|
+
return false;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2197
2316
|
/** Repete a última pergunta depois de um erro. Resposta que falhou não entra no histórico do backend. */
|
|
2198
2317
|
retry() {
|
|
2199
2318
|
const entries = this.entries();
|
|
@@ -2216,7 +2335,7 @@ class SpineAiAgentService {
|
|
|
2216
2335
|
return;
|
|
2217
2336
|
}
|
|
2218
2337
|
this.running.set(true);
|
|
2219
|
-
this.listen(answerId, {
|
|
2338
|
+
this.listen(answerId, { conversationId: this.conversationId }, text);
|
|
2220
2339
|
}
|
|
2221
2340
|
listen(answerId, variables, question) {
|
|
2222
2341
|
const token = this.auth.getToken();
|
|
@@ -2247,6 +2366,7 @@ class SpineAiAgentService {
|
|
|
2247
2366
|
return;
|
|
2248
2367
|
}
|
|
2249
2368
|
this.messageSent = true;
|
|
2369
|
+
this.watchCredential(http, variables, token);
|
|
2250
2370
|
sendAgentMessage(http, token, variables, question).then(({ status, message }) => { if (status >= 400)
|
|
2251
2371
|
this.fail(answerId, message || this.text.sendError); }, () => this.fail(answerId, this.text.sendError));
|
|
2252
2372
|
}
|
|
@@ -2340,7 +2460,27 @@ class SpineAiAgentService {
|
|
|
2340
2460
|
}));
|
|
2341
2461
|
this.finish();
|
|
2342
2462
|
}
|
|
2463
|
+
/**
|
|
2464
|
+
* O login renova o token da pessoa a cada poucos minutos, mas a resposta começou com o do envio e pode levar mais tempo que
|
|
2465
|
+
* ele. Enquanto ela está em andamento, cada token novo vai para o agente, que troca antes da próxima consulta.
|
|
2466
|
+
*/
|
|
2467
|
+
watchCredential(http, variables, token) {
|
|
2468
|
+
this.credentialSent = token;
|
|
2469
|
+
if (this.credentialTimer)
|
|
2470
|
+
clearInterval(this.credentialTimer);
|
|
2471
|
+
this.credentialTimer = setInterval(() => {
|
|
2472
|
+
const current = this.auth.getToken();
|
|
2473
|
+
if (!current || current === this.credentialSent)
|
|
2474
|
+
return;
|
|
2475
|
+
this.credentialSent = current;
|
|
2476
|
+
refreshAgentCredential(http, current, variables).catch(() => undefined);
|
|
2477
|
+
}, CREDENTIAL_CHECK_MS);
|
|
2478
|
+
}
|
|
2343
2479
|
finish() {
|
|
2480
|
+
if (this.credentialTimer)
|
|
2481
|
+
clearInterval(this.credentialTimer);
|
|
2482
|
+
this.credentialTimer = null;
|
|
2483
|
+
this.credentialSent = null;
|
|
2344
2484
|
if (this.silence)
|
|
2345
2485
|
clearTimeout(this.silence);
|
|
2346
2486
|
if (this.reconnectTimer)
|
|
@@ -2386,12 +2526,12 @@ class SpineAiAgentDockButtonComponent {
|
|
|
2386
2526
|
}
|
|
2387
2527
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentDockButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2388
2528
|
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.
|
|
2529
|
+
@if (agent.available() && agent.docked()) {
|
|
2390
2530
|
<hm-icon-button
|
|
2391
2531
|
[class]="dockClass"
|
|
2392
2532
|
emphasis="subtle"
|
|
2393
2533
|
icon="chat-bubble"
|
|
2394
|
-
[label]="agent.panelOpen() ? text.close : agent.title"
|
|
2534
|
+
[label]="agent.panelOpen() ? text.close : agent.title()"
|
|
2395
2535
|
(clicked)="agent.togglePanel()"
|
|
2396
2536
|
/>
|
|
2397
2537
|
}
|
|
@@ -2404,12 +2544,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
2404
2544
|
imports: [IconButtonComponent],
|
|
2405
2545
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2406
2546
|
template: `
|
|
2407
|
-
@if (agent.
|
|
2547
|
+
@if (agent.available() && agent.docked()) {
|
|
2408
2548
|
<hm-icon-button
|
|
2409
2549
|
[class]="dockClass"
|
|
2410
2550
|
emphasis="subtle"
|
|
2411
2551
|
icon="chat-bubble"
|
|
2412
|
-
[label]="agent.panelOpen() ? text.close : agent.title"
|
|
2552
|
+
[label]="agent.panelOpen() ? text.close : agent.title()"
|
|
2413
2553
|
(clicked)="agent.togglePanel()"
|
|
2414
2554
|
/>
|
|
2415
2555
|
}
|
|
@@ -7558,7 +7698,7 @@ function registerPage() {
|
|
|
7558
7698
|
route_redirect: SPINE_SUGGESTIONS_PAGE_PATH,
|
|
7559
7699
|
description: text.description,
|
|
7560
7700
|
},
|
|
7561
|
-
load: () => import('./trismegisto-spine-layout-suggestions-page.component-
|
|
7701
|
+
load: () => import('./trismegisto-spine-layout-suggestions-page.component-Cj4KgMj3.mjs').then((m) => m.SpineSuggestionsPageComponent),
|
|
7562
7702
|
});
|
|
7563
7703
|
});
|
|
7564
7704
|
}
|
|
@@ -8703,12 +8843,16 @@ class SpineAiAgentLauncherComponent {
|
|
|
8703
8843
|
constructor() {
|
|
8704
8844
|
this.agent = inject(SpineAiAgentService);
|
|
8705
8845
|
this.text = inject(SPINE_MESSAGES).aiAgentChat;
|
|
8846
|
+
this.toast = inject(HermesToastService);
|
|
8706
8847
|
this.panelId = PANEL_ID;
|
|
8707
8848
|
this.titleId = TITLE_ID;
|
|
8708
8849
|
/** Tamanho do painel: continua como a pessoa deixou ao fechar e abrir. */
|
|
8709
8850
|
this.expanded = signal(readPanelState().expanded === true, ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
8710
8851
|
this.chat = signal(null, ...(ngDevMode ? [{ debugName: "chat" }] : /* istanbul ignore next */ []));
|
|
8711
8852
|
this.dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
|
|
8853
|
+
this.started = false;
|
|
8854
|
+
// Lido na criação: o efeito que guarda o estado roda antes do balão aparecer e gravaria "fechado" por cima.
|
|
8855
|
+
this.initialPanel = readPanelState();
|
|
8712
8856
|
/** Com o balão na metade de cima da tela, o painel abre para baixo, onde há espaço. */
|
|
8713
8857
|
this.below = computed(() => !this.agent.docked() && (this.agent.position()?.bottom ?? 0) > window.innerHeight / 2, ...(ngDevMode ? [{ debugName: "below" }] : /* istanbul ignore next */ []));
|
|
8714
8858
|
/** O balão é um círculo: o rosto cabe nele, e uma imagem de corpo inteiro cortaria onde não deve. */
|
|
@@ -8743,12 +8887,24 @@ class SpineAiAgentLauncherComponent {
|
|
|
8743
8887
|
if (this.agent.panelOpen())
|
|
8744
8888
|
untracked(() => void this.prepare());
|
|
8745
8889
|
});
|
|
8890
|
+
// O balão só existe depois que o gateway confirma o agente: aí o painel volta como estava e o convite aparece.
|
|
8891
|
+
effect(() => {
|
|
8892
|
+
if (this.agent.available())
|
|
8893
|
+
untracked(() => this.start());
|
|
8894
|
+
});
|
|
8746
8895
|
}
|
|
8747
8896
|
ngOnInit() {
|
|
8897
|
+
if (this.agent.enabled)
|
|
8898
|
+
this.agent.checkAvailability();
|
|
8899
|
+
}
|
|
8900
|
+
start() {
|
|
8901
|
+
if (this.started)
|
|
8902
|
+
return;
|
|
8903
|
+
this.started = true;
|
|
8748
8904
|
// Sem roubar o foco: a pessoa acabou de recarregar a tela, não pediu o chat agora.
|
|
8749
|
-
if (this.
|
|
8905
|
+
if (this.initialPanel.open === true)
|
|
8750
8906
|
this.agent.panelOpen.set(true);
|
|
8751
|
-
if (this.
|
|
8907
|
+
if (this.greetingVisible()) {
|
|
8752
8908
|
this.agent.loadSuggestions();
|
|
8753
8909
|
this.greetingTimer = setTimeout(() => this.closeGreeting(), GREETING_MS);
|
|
8754
8910
|
}
|
|
@@ -8761,7 +8917,7 @@ class SpineAiAgentLauncherComponent {
|
|
|
8761
8917
|
const focus = this.agent.focusOnOpen;
|
|
8762
8918
|
this.agent.focusOnOpen = false;
|
|
8763
8919
|
if (!this.chat()) {
|
|
8764
|
-
const { SpineAiAgentChatComponent } = await import('./trismegisto-spine-layout-ai-agent-chat.component-
|
|
8920
|
+
const { SpineAiAgentChatComponent } = await import('./trismegisto-spine-layout-ai-agent-chat.component-Brh0_LS9.mjs');
|
|
8765
8921
|
this.chat.set(SpineAiAgentChatComponent);
|
|
8766
8922
|
}
|
|
8767
8923
|
this.agent.loadSuggestions();
|
|
@@ -8797,6 +8953,10 @@ class SpineAiAgentLauncherComponent {
|
|
|
8797
8953
|
this.agent.dock();
|
|
8798
8954
|
afterNextRender(() => this.focusEntry(), { injector: this.injector });
|
|
8799
8955
|
}
|
|
8956
|
+
async downloadTranscript() {
|
|
8957
|
+
if (!(await this.agent.downloadTranscript()))
|
|
8958
|
+
this.toast.error(this.text.transcriptDownloadError);
|
|
8959
|
+
}
|
|
8800
8960
|
undock() {
|
|
8801
8961
|
this.agent.undock();
|
|
8802
8962
|
afterNextRender(() => this.focusEntry(), { injector: this.injector });
|
|
@@ -8844,7 +9004,7 @@ class SpineAiAgentLauncherComponent {
|
|
|
8844
9004
|
}
|
|
8845
9005
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAiAgentLauncherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8846
9006
|
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.
|
|
9007
|
+
@if (agent.available()) {
|
|
8848
9008
|
@if (chat(); as component) {
|
|
8849
9009
|
<section
|
|
8850
9010
|
class="spine-agent-panel"
|
|
@@ -8861,7 +9021,7 @@ class SpineAiAgentLauncherComponent {
|
|
|
8861
9021
|
@if (agent.avatarNow(); as avatar) {
|
|
8862
9022
|
<img class="spine-agent-panel__avatar" [src]="avatar" alt="" (error)="agent.imageFailed(avatar)" />
|
|
8863
9023
|
}
|
|
8864
|
-
<h2 class="spine-agent-panel__title" [attr.id]="titleId">{{ agent.title }}</h2>
|
|
9024
|
+
<h2 class="spine-agent-panel__title" [attr.id]="titleId">{{ agent.title() }}</h2>
|
|
8865
9025
|
<hm-icon-button
|
|
8866
9026
|
emphasis="subtle"
|
|
8867
9027
|
size="small"
|
|
@@ -8870,6 +9030,14 @@ class SpineAiAgentLauncherComponent {
|
|
|
8870
9030
|
[disabled]="agent.running() || !agent.entries().length"
|
|
8871
9031
|
(clicked)="agent.reset()"
|
|
8872
9032
|
/>
|
|
9033
|
+
<hm-icon-button
|
|
9034
|
+
emphasis="subtle"
|
|
9035
|
+
size="small"
|
|
9036
|
+
icon="download"
|
|
9037
|
+
[label]="text.transcriptDownload"
|
|
9038
|
+
[disabled]="agent.running() || !agent.entries().length"
|
|
9039
|
+
(clicked)="downloadTranscript()"
|
|
9040
|
+
/>
|
|
8873
9041
|
<hm-icon-button
|
|
8874
9042
|
emphasis="subtle"
|
|
8875
9043
|
size="small"
|
|
@@ -8895,7 +9063,7 @@ class SpineAiAgentLauncherComponent {
|
|
|
8895
9063
|
[class.spine-agent-greeting--after]="greetingAfter()"
|
|
8896
9064
|
[class.spine-agent-greeting--below]="below()"
|
|
8897
9065
|
[class.spine-agent-greeting--figure]="agent.image()"
|
|
8898
|
-
[attr.aria-label]="agent.title"
|
|
9066
|
+
[attr.aria-label]="agent.title()"
|
|
8899
9067
|
(pointerdown)="$event.stopPropagation()"
|
|
8900
9068
|
(mouseenter)="keepGreeting()"
|
|
8901
9069
|
(focusin)="keepGreeting()"
|
|
@@ -8905,10 +9073,10 @@ class SpineAiAgentLauncherComponent {
|
|
|
8905
9073
|
}
|
|
8906
9074
|
<div class="spine-agent-greeting__content">
|
|
8907
9075
|
<div class="spine-agent-greeting__head">
|
|
8908
|
-
<p class="spine-agent-greeting__title">{{ agent.greetingTitle }}</p>
|
|
9076
|
+
<p class="spine-agent-greeting__title">{{ agent.greetingTitle() }}</p>
|
|
8909
9077
|
<hm-icon-button emphasis="subtle" size="small" icon="times" [label]="text.greetingClose" (clicked)="closeGreeting()" />
|
|
8910
9078
|
</div>
|
|
8911
|
-
<p class="spine-agent-greeting__text">{{ agent.greetingText }}</p>
|
|
9079
|
+
<p class="spine-agent-greeting__text">{{ agent.greetingText() }}</p>
|
|
8912
9080
|
@if (greetingSuggestions().length) {
|
|
8913
9081
|
<hm-prompt-suggestions [suggestions]="greetingSuggestions()" (chosen)="askFromGreeting($event)" />
|
|
8914
9082
|
}
|
|
@@ -8917,7 +9085,7 @@ class SpineAiAgentLauncherComponent {
|
|
|
8917
9085
|
}
|
|
8918
9086
|
<hm-fab
|
|
8919
9087
|
[fixed]="false"
|
|
8920
|
-
[label]="agent.panelOpen() ? text.close : agent.title"
|
|
9088
|
+
[label]="agent.panelOpen() ? text.close : agent.title()"
|
|
8921
9089
|
[icon]="agent.panelOpen() ? 'times' : fabImage() ? '' : 'chat-bubble'"
|
|
8922
9090
|
[expanded]="agent.panelOpen()"
|
|
8923
9091
|
[controls]="panelId"
|
|
@@ -8951,7 +9119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
8951
9119
|
'[style.--spine-agent-end]': "agent.position() ? agent.position()!.end + 'px' : null",
|
|
8952
9120
|
'[style.--spine-agent-bottom]': "agent.position() ? agent.position()!.bottom + 'px' : null",
|
|
8953
9121
|
}, template: `
|
|
8954
|
-
@if (agent.
|
|
9122
|
+
@if (agent.available()) {
|
|
8955
9123
|
@if (chat(); as component) {
|
|
8956
9124
|
<section
|
|
8957
9125
|
class="spine-agent-panel"
|
|
@@ -8968,7 +9136,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
8968
9136
|
@if (agent.avatarNow(); as avatar) {
|
|
8969
9137
|
<img class="spine-agent-panel__avatar" [src]="avatar" alt="" (error)="agent.imageFailed(avatar)" />
|
|
8970
9138
|
}
|
|
8971
|
-
<h2 class="spine-agent-panel__title" [attr.id]="titleId">{{ agent.title }}</h2>
|
|
9139
|
+
<h2 class="spine-agent-panel__title" [attr.id]="titleId">{{ agent.title() }}</h2>
|
|
8972
9140
|
<hm-icon-button
|
|
8973
9141
|
emphasis="subtle"
|
|
8974
9142
|
size="small"
|
|
@@ -8977,6 +9145,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
8977
9145
|
[disabled]="agent.running() || !agent.entries().length"
|
|
8978
9146
|
(clicked)="agent.reset()"
|
|
8979
9147
|
/>
|
|
9148
|
+
<hm-icon-button
|
|
9149
|
+
emphasis="subtle"
|
|
9150
|
+
size="small"
|
|
9151
|
+
icon="download"
|
|
9152
|
+
[label]="text.transcriptDownload"
|
|
9153
|
+
[disabled]="agent.running() || !agent.entries().length"
|
|
9154
|
+
(clicked)="downloadTranscript()"
|
|
9155
|
+
/>
|
|
8980
9156
|
<hm-icon-button
|
|
8981
9157
|
emphasis="subtle"
|
|
8982
9158
|
size="small"
|
|
@@ -9002,7 +9178,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
9002
9178
|
[class.spine-agent-greeting--after]="greetingAfter()"
|
|
9003
9179
|
[class.spine-agent-greeting--below]="below()"
|
|
9004
9180
|
[class.spine-agent-greeting--figure]="agent.image()"
|
|
9005
|
-
[attr.aria-label]="agent.title"
|
|
9181
|
+
[attr.aria-label]="agent.title()"
|
|
9006
9182
|
(pointerdown)="$event.stopPropagation()"
|
|
9007
9183
|
(mouseenter)="keepGreeting()"
|
|
9008
9184
|
(focusin)="keepGreeting()"
|
|
@@ -9012,10 +9188,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
9012
9188
|
}
|
|
9013
9189
|
<div class="spine-agent-greeting__content">
|
|
9014
9190
|
<div class="spine-agent-greeting__head">
|
|
9015
|
-
<p class="spine-agent-greeting__title">{{ agent.greetingTitle }}</p>
|
|
9191
|
+
<p class="spine-agent-greeting__title">{{ agent.greetingTitle() }}</p>
|
|
9016
9192
|
<hm-icon-button emphasis="subtle" size="small" icon="times" [label]="text.greetingClose" (clicked)="closeGreeting()" />
|
|
9017
9193
|
</div>
|
|
9018
|
-
<p class="spine-agent-greeting__text">{{ agent.greetingText }}</p>
|
|
9194
|
+
<p class="spine-agent-greeting__text">{{ agent.greetingText() }}</p>
|
|
9019
9195
|
@if (greetingSuggestions().length) {
|
|
9020
9196
|
<hm-prompt-suggestions [suggestions]="greetingSuggestions()" (chosen)="askFromGreeting($event)" />
|
|
9021
9197
|
}
|
|
@@ -9024,7 +9200,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
9024
9200
|
}
|
|
9025
9201
|
<hm-fab
|
|
9026
9202
|
[fixed]="false"
|
|
9027
|
-
[label]="agent.panelOpen() ? text.close : agent.title"
|
|
9203
|
+
[label]="agent.panelOpen() ? text.close : agent.title()"
|
|
9028
9204
|
[icon]="agent.panelOpen() ? 'times' : fabImage() ? '' : 'chat-bubble'"
|
|
9029
9205
|
[expanded]="agent.panelOpen()"
|
|
9030
9206
|
[controls]="panelId"
|
|
@@ -9716,4 +9892,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
|
|
|
9716
9892
|
*/
|
|
9717
9893
|
|
|
9718
9894
|
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-
|
|
9895
|
+
//# sourceMappingURL=trismegisto-spine-layout-trismegisto-spine-layout-UM7CC7Sj.mjs.map
|