@vertesia/ui 1.6.0-dev.20260912.110338Z → 1.6.0-dev.20260918.061604Z
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/lib/env/index.d.ts +4 -0
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +4 -0
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
- package/lib/features/agent/PayloadBuilder.js +6 -1
- package/lib/features/agent/PayloadBuilder.js.map +1 -1
- package/lib/features/agent/chat/AgentRunFeedback.d.ts +7 -0
- package/lib/features/agent/chat/AgentRunFeedback.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRunFeedback.js +13 -1
- package/lib/features/agent/chat/AgentRunFeedback.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +4 -3
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +2 -2
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -2
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.d.ts +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentPlans.js +5 -5
- package/lib/features/agent/chat/hooks/useAgentPlans.js.map +1 -1
- package/lib/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
- package/lib/features/store/objects/search/DocumentSearchContext.js +6 -1
- package/lib/features/store/objects/search/DocumentSearchContext.js.map +1 -1
- package/lib/features/user/UserInfo.d.ts +2 -0
- package/lib/features/user/UserInfo.d.ts.map +1 -1
- package/lib/session/UserSessionProvider.js +3 -3
- package/lib/session/UserSessionProvider.js.map +1 -1
- package/lib/session/auth/firebase.d.ts.map +1 -1
- package/lib/session/auth/firebase.js +10 -0
- package/lib/session/auth/firebase.js.map +1 -1
- package/lib/shell/login/SignInAuthPending.js +1 -1
- package/lib/shell/login/SignInAuthPending.js.map +1 -1
- package/lib/shell/login/SignInEmailStep.d.ts.map +1 -1
- package/lib/shell/login/SignInEmailStep.js +2 -2
- package/lib/shell/login/SignInEmailStep.js.map +1 -1
- package/lib/shell/login/SignInIcons.d.ts +1 -1
- package/lib/shell/login/SignInIcons.d.ts.map +1 -1
- package/lib/shell/login/signInUtils.d.ts +1 -1
- package/lib/shell/login/signInUtils.d.ts.map +1 -1
- package/lib/shell/login/signInUtils.js +5 -6
- package/lib/shell/login/signInUtils.js.map +1 -1
- package/lib/vertesia-ui-core.js +83 -41
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +336 -317
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-layout.js +43 -19
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js +123 -74
- package/lib/vertesia-ui-rich-text.js.map +1 -1
- package/lib/vertesia-ui-router.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +57 -28
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +153 -97
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts +1 -1
- package/package.json +20 -20
- package/src/env/index.test.ts +27 -0
- package/src/env/index.ts +7 -0
- package/src/features/agent/PayloadBuilder.tsx +6 -1
- package/src/features/agent/chat/AgentRunFeedback.test.tsx +18 -1
- package/src/features/agent/chat/AgentRunFeedback.tsx +13 -0
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +1 -1
- package/src/features/agent/chat/ModernAgentConversation.tsx +6 -5
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +2 -2
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +2 -2
- package/src/features/agent/chat/hooks/useAgentPlans.test.ts +52 -0
- package/src/features/agent/chat/hooks/useAgentPlans.ts +5 -5
- package/src/features/store/objects/search/DocumentSearchContext.ts +8 -3
- package/src/router/path.test.ts +5 -5
- package/src/session/UserSessionProvider.tsx +3 -3
- package/src/session/auth/firebase.test.ts +25 -0
- package/src/session/auth/firebase.ts +10 -0
- package/src/shell/login/SignInAuthPending.tsx +3 -3
- package/src/shell/login/SignInEmailStep.test.tsx +32 -0
- package/src/shell/login/SignInEmailStep.tsx +13 -9
- package/src/shell/login/signInUtils.ts +12 -4
- package/src/widgets/markdown/CollaborativeMarkdownRenderer.test.tsx +11 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertesia/ui",
|
|
3
|
-
"version": "1.6.0-dev.
|
|
3
|
+
"version": "1.6.0-dev.20260918.061604Z",
|
|
4
4
|
"description": "Vertesia UI components and and hooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"@radix-ui/react-switch": "^1.3.7",
|
|
46
46
|
"@radix-ui/react-tabs": "^1.1.21",
|
|
47
47
|
"@radix-ui/react-tooltip": "^1.2.16",
|
|
48
|
-
"@vertesia/client": "1.6.0-dev.
|
|
49
|
-
"@vertesia/common": "1.6.0-dev.
|
|
50
|
-
"@vertesia/fusion-ux": "1.6.0-dev.
|
|
51
|
-
"@vertesia/json": "1.6.0-dev.
|
|
52
|
-
"@vertesia/rich-text": "1.6.0-dev.
|
|
48
|
+
"@vertesia/client": "1.6.0-dev.20260918.061604Z",
|
|
49
|
+
"@vertesia/common": "1.6.0-dev.20260918.061604Z",
|
|
50
|
+
"@vertesia/fusion-ux": "1.6.0-dev.20260918.061604Z",
|
|
51
|
+
"@vertesia/json": "1.6.0-dev.20260918.061604Z",
|
|
52
|
+
"@vertesia/rich-text": "1.6.0-dev.20260918.061604Z",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
55
55
|
"aria-hidden": "^1.2.6",
|
|
@@ -58,30 +58,30 @@
|
|
|
58
58
|
"cmdk": "^1.1.1",
|
|
59
59
|
"dayjs": "^1.11.23",
|
|
60
60
|
"debounce": "^3.0.0",
|
|
61
|
-
"dompurify": "^3.4.
|
|
61
|
+
"dompurify": "^3.4.15",
|
|
62
62
|
"fast-xml-parser": "^5.11.1",
|
|
63
63
|
"firebase": "^12.18.0",
|
|
64
|
-
"framer-motion": "^13.
|
|
65
|
-
"i18next": "^26.4.
|
|
64
|
+
"framer-motion": "^13.2.0",
|
|
65
|
+
"i18next": "^26.4.2",
|
|
66
66
|
"jwt-decode": "^4.0.0",
|
|
67
|
-
"katex": "^0.18.
|
|
67
|
+
"katex": "^0.18.7",
|
|
68
68
|
"lodash-es": "^4.18.1",
|
|
69
|
-
"lucide-react": "^1.
|
|
69
|
+
"lucide-react": "^1.42.0",
|
|
70
70
|
"mermaid": "^11.17.2",
|
|
71
71
|
"monaco-editor": "^0.56.0",
|
|
72
|
-
"motion": "^13.
|
|
72
|
+
"motion": "^13.2.0",
|
|
73
73
|
"papaparse": "^5.7.0",
|
|
74
74
|
"react": "19.2.8",
|
|
75
75
|
"react-calendar": "^6.0.1",
|
|
76
76
|
"react-date-picker": "^12.1.0",
|
|
77
77
|
"react-dom": "19.2.8",
|
|
78
|
-
"react-error-boundary": "^6.1.
|
|
79
|
-
"react-i18next": "^17.0.
|
|
78
|
+
"react-error-boundary": "^6.1.5",
|
|
79
|
+
"react-i18next": "^17.0.13",
|
|
80
80
|
"react-markdown": "^10.1.0",
|
|
81
81
|
"react-pdf": "^10.5.0",
|
|
82
82
|
"react-remove-scroll": "^2.7.2",
|
|
83
83
|
"react-remove-scroll-bar": "^2.3.8",
|
|
84
|
-
"react-resizable-panels": "^4.12.
|
|
84
|
+
"react-resizable-panels": "^4.12.4",
|
|
85
85
|
"react-style-singleton": "^2.2.3",
|
|
86
86
|
"react-vega": "^8.0.0",
|
|
87
87
|
"rehype-katex": "^7.0.1",
|
|
@@ -106,16 +106,16 @@
|
|
|
106
106
|
"@types/node": "^24.13.3",
|
|
107
107
|
"@types/papaparse": "^5.5.2",
|
|
108
108
|
"@types/react": "19.2.18",
|
|
109
|
-
"@types/react-dom": "19.2.
|
|
109
|
+
"@types/react-dom": "19.2.7",
|
|
110
110
|
"@vertesia/tsconfig": "0.1.0",
|
|
111
111
|
"axe-core": "^4.13.0",
|
|
112
112
|
"culori": "^4.0.2",
|
|
113
|
-
"i18next-cli": "^1.
|
|
113
|
+
"i18next-cli": "^1.73.1",
|
|
114
114
|
"jsdom": "^30.0.1",
|
|
115
|
-
"rolldown": "1.2.
|
|
115
|
+
"rolldown": "1.2.8",
|
|
116
116
|
"tailwindcss": "^4.3.3",
|
|
117
117
|
"typescript": "7.0.2",
|
|
118
|
-
"vitest": "
|
|
118
|
+
"vitest": "5.0.0",
|
|
119
119
|
"vitest-axe": "^0.1.0",
|
|
120
120
|
"wcag-contrast": "^3.0.0"
|
|
121
121
|
},
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
"url": "https://github.com/vertesia/composableai.git",
|
|
189
189
|
"directory": "packages/ui"
|
|
190
190
|
},
|
|
191
|
-
"gitHead": "
|
|
191
|
+
"gitHead": "ea552337206e77c256478b76e19b4235e70bf769",
|
|
192
192
|
"scripts": {
|
|
193
193
|
"clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
|
|
194
194
|
"clean": "rimraf ./node_modules ./lib ./tsconfig.tsbuildinfo",
|
package/src/env/index.test.ts
CHANGED
|
@@ -192,3 +192,30 @@ describe('build-time default workspace', () => {
|
|
|
192
192
|
expect(env.defaultAuthSelection).toEqual({ accountId: 'explicit' });
|
|
193
193
|
});
|
|
194
194
|
});
|
|
195
|
+
|
|
196
|
+
describe('fixed Firebase tenant', () => {
|
|
197
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
198
|
+
|
|
199
|
+
it('combines an app tenant with gateway credentials', () => {
|
|
200
|
+
vi.stubGlobal('window', {
|
|
201
|
+
__VERTESIA_RUNTIME_CONFIG__: {
|
|
202
|
+
authMode: 'firebase',
|
|
203
|
+
firebase: {
|
|
204
|
+
apiKey: 'gateway',
|
|
205
|
+
authDomain: 'app.example.com',
|
|
206
|
+
projectId: 'project',
|
|
207
|
+
appId: 'app',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
const env = new VertesiaEnvironment().init(baseProps, { VITE_FIREBASE_TENANT_ID: ' example-tenant ' });
|
|
212
|
+
expect(env.firebase?.tenantId).toBe('example-tenant');
|
|
213
|
+
expect(env.firebase?.apiKey).toBe('gateway');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('does not enable Firebase just because a tenant is supplied', () => {
|
|
217
|
+
vi.stubGlobal('window', {});
|
|
218
|
+
const env = new VertesiaEnvironment().init(baseProps, { VITE_FIREBASE_TENANT_ID: 'tenant' });
|
|
219
|
+
expect(env.firebase).toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
});
|
package/src/env/index.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface EnvProps {
|
|
|
34
34
|
projectId: string;
|
|
35
35
|
appId?: string;
|
|
36
36
|
providerType?: string;
|
|
37
|
+
/** Fixed Identity Platform tenant, preserved throughout sign-in. */
|
|
38
|
+
tenantId?: string;
|
|
37
39
|
};
|
|
38
40
|
/** Default workspace selection when the URL does not explicitly select an account or project. */
|
|
39
41
|
defaultAuthSelection?: { accountId?: string; projectId?: string };
|
|
@@ -139,6 +141,11 @@ export class VertesiaEnvironment implements Readonly<EnvProps> {
|
|
|
139
141
|
const runtimeConfig = injectedRuntimeConfig() ?? buildRuntimeConfig(buildEnv);
|
|
140
142
|
const runtimeFirebase = runtimeConfig?.authMode === 'firebase' ? runtimeConfig.firebase : undefined;
|
|
141
143
|
this._props = props && runtimeFirebase && !props.firebase ? { ...props, firebase: runtimeFirebase } : props;
|
|
144
|
+
const tenantId =
|
|
145
|
+
typeof buildEnv?.VITE_FIREBASE_TENANT_ID === 'string' ? buildEnv.VITE_FIREBASE_TENANT_ID.trim() : '';
|
|
146
|
+
if (this._props?.firebase && tenantId && !this._props.firebase.tenantId) {
|
|
147
|
+
this._props = { ...this._props, firebase: { ...this._props.firebase, tenantId } };
|
|
148
|
+
}
|
|
142
149
|
if (this._props && this._props.defaultAuthSelection === undefined && buildEnv) {
|
|
143
150
|
const accountId =
|
|
144
151
|
typeof buildEnv.VITE_VERTESIA_ACCOUNT_ID === 'string'
|
|
@@ -332,7 +332,12 @@ export class PayloadBuilder {
|
|
|
332
332
|
this._model_options = interaction.model_options as ModelOptions | undefined;
|
|
333
333
|
if (interaction.runtime?.environment) {
|
|
334
334
|
const envId = interaction.runtime.environment;
|
|
335
|
-
this.vertesia.environments
|
|
335
|
+
this.vertesia.environments
|
|
336
|
+
.retrieve(envId)
|
|
337
|
+
.then((environment) => (this.environment = environment))
|
|
338
|
+
.catch(() => {
|
|
339
|
+
this.environment = undefined;
|
|
340
|
+
});
|
|
336
341
|
}
|
|
337
342
|
}
|
|
338
343
|
this.onStateChanged();
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import type { AgentRunFeedbackEntry, AgentRunFeedbackPayload, AgentRunFeedbackStatus } from '@vertesia/common';
|
|
3
|
+
import { AgentMessageType } from '@vertesia/common';
|
|
3
4
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
5
|
import { renderWithProviders } from '../../../__tests__/test-utils.js';
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
AgentRunFeedback,
|
|
8
|
+
AgentRunFeedbackProvider,
|
|
9
|
+
agentRunFeedbackReasonCodes,
|
|
10
|
+
isAgentMessageRatable,
|
|
11
|
+
} from './AgentRunFeedback';
|
|
6
12
|
|
|
7
13
|
const mocks = vi.hoisted(() => {
|
|
8
14
|
const recordFeedback = vi.fn();
|
|
@@ -38,6 +44,17 @@ function payloadOf(call: number): AgentRunFeedbackPayload {
|
|
|
38
44
|
return mocks.recordFeedback.mock.calls[call][1] as AgentRunFeedbackPayload;
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
describe('isAgentMessageRatable', () => {
|
|
48
|
+
it('offers a rating on a final answer only, never on a streaming placeholder', () => {
|
|
49
|
+
expect(isAgentMessageRatable({ type: AgentMessageType.ANSWER, details: { streamed: true } })).toBe(true);
|
|
50
|
+
expect(isAgentMessageRatable({ type: AgentMessageType.ANSWER, details: undefined })).toBe(true);
|
|
51
|
+
expect(isAgentMessageRatable({ type: AgentMessageType.ANSWER, details: { source: 'streaming_summary' } })).toBe(
|
|
52
|
+
false,
|
|
53
|
+
);
|
|
54
|
+
expect(isAgentMessageRatable({ type: AgentMessageType.THOUGHT, details: undefined })).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
41
58
|
describe('AgentRunFeedback', () => {
|
|
42
59
|
beforeEach(() => {
|
|
43
60
|
mocks.recordFeedback.mockReset();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AGENT_RUN_FEEDBACK_COMMENT_MAX_LENGTH,
|
|
3
3
|
type AgentMessage,
|
|
4
|
+
AgentMessageType,
|
|
4
5
|
type AgentRunFeedbackEntry,
|
|
5
6
|
type AgentRunFeedbackPayload,
|
|
6
7
|
type AgentRunFeedbackRating,
|
|
@@ -61,6 +62,18 @@ export function agentMessageFeedbackId(message: Pick<AgentMessage, 'workstream_i
|
|
|
61
62
|
return `${message.workstream_id || 'main'}:${message.timestamp}`;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Whether a transcript message can carry a rating. Only a final answer can: while an answer is
|
|
67
|
+
* still streaming, the transcript shows a placeholder stamped with the browser clock, and a
|
|
68
|
+
* rating keyed to that timestamp would never match the answer the worker eventually posts, so
|
|
69
|
+
* the same answer would end up rated twice.
|
|
70
|
+
*/
|
|
71
|
+
export function isAgentMessageRatable(message: Pick<AgentMessage, 'type' | 'details'>): boolean {
|
|
72
|
+
if (message.type !== AgentMessageType.ANSWER) return false;
|
|
73
|
+
const details = message.details as { source?: unknown } | undefined;
|
|
74
|
+
return details?.source !== 'streaming_summary';
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
/** Idempotency key for one submission; the server counts a retried id once. */
|
|
65
78
|
function newFeedbackId(): string {
|
|
66
79
|
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) return crypto.randomUUID();
|
|
@@ -1278,7 +1278,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
1278
1278
|
expect.objectContaining({ workstream_id: 'beta', status: 'running' }),
|
|
1279
1279
|
expect.objectContaining({ workstream_id: 'gamma', status: 'completed' }),
|
|
1280
1280
|
]);
|
|
1281
|
-
expect(latestRightPanelProps.activeTab).toBe('
|
|
1281
|
+
expect(latestRightPanelProps.activeTab).toBe('workstreams');
|
|
1282
1282
|
expect(latestMessageInputProps.activeTaskCount).toBe(2);
|
|
1283
1283
|
expect(latestMessageInputProps.activeWorkstreams).toEqual([
|
|
1284
1284
|
expect.objectContaining({ workstream_id: 'alpha', status: 'running' }),
|
|
@@ -1554,7 +1554,7 @@ function ModernAgentConversationInner({
|
|
|
1554
1554
|
showInput,
|
|
1555
1555
|
showSlidingPanel,
|
|
1556
1556
|
setShowSlidingPanel,
|
|
1557
|
-
} = useAgentPlans(messages, interactive
|
|
1557
|
+
} = useAgentPlans(messages, interactive);
|
|
1558
1558
|
|
|
1559
1559
|
const {
|
|
1560
1560
|
openDocuments,
|
|
@@ -1938,9 +1938,10 @@ function ModernAgentConversationInner({
|
|
|
1938
1938
|
// Unified right panel state
|
|
1939
1939
|
// ────────────────────────────────────────────
|
|
1940
1940
|
type RightPanelTab = 'plan' | 'workstreams' | 'documents' | 'uploads' | 'artifacts' | 'payload' | 'conversation';
|
|
1941
|
-
const [rightPanelTab, setRightPanelTab] = useState<RightPanelTab>(
|
|
1942
|
-
conversationContent || conversationTab ? 'conversation' :
|
|
1941
|
+
const [rightPanelTab, setRightPanelTab] = useState<RightPanelTab | undefined>(
|
|
1942
|
+
conversationContent || conversationTab ? 'conversation' : undefined,
|
|
1943
1943
|
);
|
|
1944
|
+
const defaultRightPanelTab = plans.length > 0 || panelWorkstreams.length === 0 ? 'plan' : 'workstreams';
|
|
1944
1945
|
const [selectedArtifactPath, setSelectedArtifactPath] = useState<string | null>(null);
|
|
1945
1946
|
const [rightPanelWidth, setRightPanelWidth] = useState(400);
|
|
1946
1947
|
const [isRightPanelResizing, setIsRightPanelResizing] = useState(false);
|
|
@@ -3085,8 +3086,8 @@ function ModernAgentConversationInner({
|
|
|
3085
3086
|
conversationContent={conversationTab ? conversationAreaJsx : conversationContent}
|
|
3086
3087
|
// Panel control
|
|
3087
3088
|
onClose={handleCloseRightPanel}
|
|
3088
|
-
defaultTab={rightPanelTab}
|
|
3089
|
-
activeTab={rightPanelTab}
|
|
3089
|
+
defaultTab={rightPanelTab ?? defaultRightPanelTab}
|
|
3090
|
+
activeTab={rightPanelTab ?? defaultRightPanelTab}
|
|
3090
3091
|
onTabChange={setRightPanelTab}
|
|
3091
3092
|
/>
|
|
3092
3093
|
</div>
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
Wrench,
|
|
30
30
|
} from 'lucide-react';
|
|
31
31
|
import React, { Component, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
32
|
-
import { AgentRunFeedback, agentMessageFeedbackId } from '../AgentRunFeedback';
|
|
32
|
+
import { AgentRunFeedback, agentMessageFeedbackId, isAgentMessageRatable } from '../AgentRunFeedback';
|
|
33
33
|
import { AnimatedThinkingDots, PulsatingCircle } from '../AnimatedThinkingDots';
|
|
34
34
|
import { AskUserWidget, isAskUserOptions } from '../AskUserWidget';
|
|
35
35
|
import { DocumentEditingActionCard, parseMarkdownEditingAction } from '../DocumentEditingActionCard.js';
|
|
@@ -823,7 +823,7 @@ function SummaryMessage({
|
|
|
823
823
|
</MarkdownRenderer>
|
|
824
824
|
</div>
|
|
825
825
|
)}
|
|
826
|
-
{feedbackAgentRunId && message
|
|
826
|
+
{feedbackAgentRunId && isAgentMessageRatable(message) && (
|
|
827
827
|
<AgentRunFeedback
|
|
828
828
|
agentRunId={feedbackAgentRunId}
|
|
829
829
|
messageId={agentMessageFeedbackId(message)}
|
|
@@ -27,7 +27,7 @@ import type React from 'react';
|
|
|
27
27
|
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
28
28
|
import dayjs from '../../../../core/utils/dayjs.js';
|
|
29
29
|
import { useDownloadFile } from '../../../store/objects/components/useDownloadFile.js';
|
|
30
|
-
import { AgentRunFeedback, agentMessageFeedbackId } from '../AgentRunFeedback';
|
|
30
|
+
import { AgentRunFeedback, agentMessageFeedbackId, isAgentMessageRatable } from '../AgentRunFeedback';
|
|
31
31
|
import { PulsatingCircle } from '../AnimatedThinkingDots';
|
|
32
32
|
import { AskUserWidget, isAskUserOptions } from '../AskUserWidget';
|
|
33
33
|
import { DocumentEditingActionCard, parseMarkdownEditingAction } from '../DocumentEditingActionCard.js';
|
|
@@ -683,7 +683,7 @@ function MessageItemComponent({
|
|
|
683
683
|
)
|
|
684
684
|
)}
|
|
685
685
|
|
|
686
|
-
{feedbackAgentRunId && message
|
|
686
|
+
{feedbackAgentRunId && isAgentMessageRatable(message) && (
|
|
687
687
|
<AgentRunFeedback
|
|
688
688
|
agentRunId={feedbackAgentRunId}
|
|
689
689
|
messageId={agentMessageFeedbackId(message)}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { act, cleanup, renderHook } from '@testing-library/react';
|
|
2
|
+
import { type AgentMessage, AgentMessageType } from '@vertesia/common';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { useAgentPlans } from './useAgentPlans.js';
|
|
5
|
+
|
|
6
|
+
const question: AgentMessage = {
|
|
7
|
+
timestamp: 1,
|
|
8
|
+
workflow_run_id: 'run-1',
|
|
9
|
+
workstream_id: 'main',
|
|
10
|
+
type: AgentMessageType.QUESTION,
|
|
11
|
+
message: 'Help with a task',
|
|
12
|
+
};
|
|
13
|
+
const plan: AgentMessage = {
|
|
14
|
+
...question,
|
|
15
|
+
timestamp: 2,
|
|
16
|
+
type: AgentMessageType.PLAN,
|
|
17
|
+
details: { plan: [{ id: 1, goal: 'Review the task', status: 'pending' }] },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('useAgentPlans sidebar visibility', () => {
|
|
21
|
+
beforeEach(() => sessionStorage.clear());
|
|
22
|
+
afterEach(cleanup);
|
|
23
|
+
|
|
24
|
+
it('stays closed while history loads and when history has no plan', () => {
|
|
25
|
+
const { result, rerender } = renderHook(({ messages }) => useAgentPlans(messages, true), {
|
|
26
|
+
initialProps: { messages: [] as AgentMessage[] },
|
|
27
|
+
});
|
|
28
|
+
expect(result.current.showSlidingPanel).toBe(false);
|
|
29
|
+
rerender({ messages: [question] });
|
|
30
|
+
expect(result.current.showSlidingPanel).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('opens when a plan arrives and resets for a new empty run', () => {
|
|
34
|
+
const { result, rerender } = renderHook(({ messages }) => useAgentPlans(messages, true), {
|
|
35
|
+
initialProps: { messages: [question] },
|
|
36
|
+
});
|
|
37
|
+
rerender({ messages: [question, plan] });
|
|
38
|
+
expect(result.current.showSlidingPanel).toBe(true);
|
|
39
|
+
expect(result.current.plans).toHaveLength(1);
|
|
40
|
+
rerender({ messages: [] });
|
|
41
|
+
expect(result.current.showSlidingPanel).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('preserves manual opening when subsequent messages have no plan', () => {
|
|
45
|
+
const { result, rerender } = renderHook(({ messages }) => useAgentPlans(messages, true), {
|
|
46
|
+
initialProps: { messages: [question] },
|
|
47
|
+
});
|
|
48
|
+
act(() => result.current.setShowSlidingPanel(true));
|
|
49
|
+
rerender({ messages: [question, { ...question, timestamp: 3, type: AgentMessageType.ANSWER }] });
|
|
50
|
+
expect(result.current.showSlidingPanel).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -18,14 +18,14 @@ export interface UseAgentPlansResult {
|
|
|
18
18
|
* Key improvement: incremental processing. Instead of scanning ALL messages
|
|
19
19
|
* on every change, tracks `lastProcessedIndex` and only scans new messages.
|
|
20
20
|
*/
|
|
21
|
-
export function useAgentPlans(messages: AgentMessage[], interactive: boolean
|
|
21
|
+
export function useAgentPlans(messages: AgentMessage[], interactive: boolean): UseAgentPlansResult {
|
|
22
22
|
const [plans, setPlans] = useState<Array<{ plan: Plan; timestamp: number }>>([]);
|
|
23
23
|
const [activePlanIndex, setActivePlanIndex] = useState<number>(0);
|
|
24
24
|
const [workstreamStatusMap, setWorkstreamStatusMap] = useState<
|
|
25
25
|
Map<number, Map<string, 'pending' | 'in_progress' | 'completed' | 'skipped'>>
|
|
26
26
|
>(new Map());
|
|
27
27
|
const [showInput, setShowInput] = useState(interactive);
|
|
28
|
-
const [showSlidingPanel, setShowSlidingPanel] = useState<boolean>(
|
|
28
|
+
const [showSlidingPanel, setShowSlidingPanel] = useState<boolean>(false);
|
|
29
29
|
|
|
30
30
|
// Incremental processing: track how far we've scanned
|
|
31
31
|
const lastProcessedIndex = useRef<number>(-1);
|
|
@@ -38,12 +38,12 @@ export function useAgentPlans(messages: AgentMessage[], interactive: boolean, is
|
|
|
38
38
|
setPlans([]);
|
|
39
39
|
setActivePlanIndex(0);
|
|
40
40
|
setWorkstreamStatusMap(new Map());
|
|
41
|
-
//
|
|
42
|
-
setShowSlidingPanel(
|
|
41
|
+
// Open automatically only once a plan is available.
|
|
42
|
+
setShowSlidingPanel(false);
|
|
43
43
|
lastProcessedIndex.current = -1;
|
|
44
44
|
knownPlanTimestamps.current.clear();
|
|
45
45
|
}
|
|
46
|
-
}, [messages.length
|
|
46
|
+
}, [messages.length]);
|
|
47
47
|
|
|
48
48
|
// Helper to determine showInput from the latest message
|
|
49
49
|
const updateShowInput = useCallback(
|
|
@@ -176,9 +176,14 @@ export class DocumentSearch implements SearchInterface {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
computeFacets(_query: ObjectSearchQuery) {
|
|
179
|
-
this._facetsRequest()
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
this._facetsRequest()
|
|
180
|
+
.then((facets) => {
|
|
181
|
+
this.facets.value = facets;
|
|
182
|
+
})
|
|
183
|
+
.catch((err: unknown) => {
|
|
184
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
185
|
+
this.result.value = { ...this.result.value, error };
|
|
186
|
+
});
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
async _search(loadMore = false, noFacets = false): Promise<boolean> {
|
package/src/router/path.test.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { getMountBasename, joinPath, stripMountBasename, withMountBasename } from './path';
|
|
3
3
|
|
|
4
4
|
// Simulate a served `<base href>` (or its absence) by stubbing document.querySelector + document.URL.
|
|
5
5
|
function setBaseHref(href: string | null, docUrl = 'https://gw.example.com/x') {
|
|
6
|
-
(
|
|
6
|
+
vi.stubGlobal('document', {
|
|
7
7
|
URL: docUrl,
|
|
8
8
|
querySelector: (sel: string) =>
|
|
9
9
|
sel === 'base[href]' && href ? ({ href: new URL(href, docUrl).href } as unknown) : null,
|
|
10
|
-
};
|
|
10
|
+
});
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const MOUNT = '/tenants/05948c_5ed5f4/apps/furniture-catalog/versions/20260620T064257113Z/app';
|
|
14
14
|
|
|
15
15
|
describe('mount basename helpers', () => {
|
|
16
16
|
afterEach(() => {
|
|
17
|
-
|
|
17
|
+
vi.unstubAllGlobals();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
describe('getMountBasename', () => {
|
|
@@ -31,7 +31,7 @@ describe('mount basename helpers', () => {
|
|
|
31
31
|
expect(getMountBasename()).toBe('');
|
|
32
32
|
});
|
|
33
33
|
it('returns "" when there is no document (SSR)', () => {
|
|
34
|
-
|
|
34
|
+
vi.stubGlobal('document', undefined);
|
|
35
35
|
expect(getMountBasename()).toBe('');
|
|
36
36
|
});
|
|
37
37
|
});
|
|
@@ -134,7 +134,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
134
134
|
session.setSession = setSession;
|
|
135
135
|
getComposableToken(selectedAccount, selectedProject, Env.devAuthToken)
|
|
136
136
|
.then((res) => {
|
|
137
|
-
session.login(res.rawToken).then(() => setSession(session.clone()));
|
|
137
|
+
return session.login(res.rawToken).then(() => setSession(session.clone()));
|
|
138
138
|
})
|
|
139
139
|
.catch((err) => {
|
|
140
140
|
if (surfaceAuthError(err)) return;
|
|
@@ -169,7 +169,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
169
169
|
}
|
|
170
170
|
getComposableToken(selectedAccount, selectedProject, token, false, shouldRedirectToCentralAuth())
|
|
171
171
|
.then((res) => {
|
|
172
|
-
session.login(res.rawToken, { loadOnboardingStatus }).then(() => {
|
|
172
|
+
return session.login(res.rawToken, { loadOnboardingStatus }).then(() => {
|
|
173
173
|
setSession(session.clone());
|
|
174
174
|
clearAuthHash();
|
|
175
175
|
});
|
|
@@ -238,7 +238,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
238
238
|
shouldRedirectToCentralAuth(),
|
|
239
239
|
)
|
|
240
240
|
.then((res) => {
|
|
241
|
-
session
|
|
241
|
+
return session
|
|
242
242
|
.login(res.rawToken, { loadOnboardingStatus })
|
|
243
243
|
.then(() => setSession(session.clone()));
|
|
244
244
|
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Env } from '@vertesia/ui/env';
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { getFirebaseAuth, setFirebaseTenant } from './firebase';
|
|
4
|
+
|
|
5
|
+
vi.mock('firebase/app', () => ({ initializeApp: vi.fn(() => ({})) }));
|
|
6
|
+
vi.mock('firebase/auth', () => ({ getAuth: vi.fn(() => ({ tenantId: null })) }));
|
|
7
|
+
|
|
8
|
+
describe('configured Firebase tenant', () => {
|
|
9
|
+
afterEach(() => vi.restoreAllMocks());
|
|
10
|
+
|
|
11
|
+
it('initializes redirect handling with the tenant and skips email discovery', async () => {
|
|
12
|
+
vi.spyOn(Env, 'firebase', 'get').mockReturnValue({
|
|
13
|
+
apiKey: 'key',
|
|
14
|
+
authDomain: 'app.example.com',
|
|
15
|
+
projectId: 'project',
|
|
16
|
+
tenantId: 'fixed-tenant',
|
|
17
|
+
});
|
|
18
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch');
|
|
19
|
+
expect(getFirebaseAuth().tenantId).toBe('fixed-tenant');
|
|
20
|
+
const tenant = await setFirebaseTenant('someone@another-company.com');
|
|
21
|
+
expect(tenant?.firebaseTenantId).toBe('fixed-tenant');
|
|
22
|
+
expect(tenant?.provider).toBe('oidc');
|
|
23
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -37,11 +37,21 @@ export function getFirebaseAnalytics(): Analytics {
|
|
|
37
37
|
export function getFirebaseAuth(): Auth {
|
|
38
38
|
if (!_firebaseAuth) {
|
|
39
39
|
_firebaseAuth = getAuth(getFirebaseApp());
|
|
40
|
+
_firebaseAuth.tenantId = Env.firebase?.tenantId ?? null;
|
|
40
41
|
}
|
|
41
42
|
return _firebaseAuth;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export async function setFirebaseTenant(tenantEmail?: string) {
|
|
46
|
+
if (Env.firebase?.tenantId) {
|
|
47
|
+
getFirebaseAuth().tenantId = Env.firebase.tenantId;
|
|
48
|
+
return {
|
|
49
|
+
firebaseTenantId: Env.firebase.tenantId,
|
|
50
|
+
provider: Env.firebase.providerType ?? 'oidc',
|
|
51
|
+
name: '',
|
|
52
|
+
label: '',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
45
55
|
if (!tenantEmail) {
|
|
46
56
|
console.log('No tenant name or email specified, skipping tenant setup');
|
|
47
57
|
return;
|
|
@@ -28,10 +28,10 @@ export default function SignInAuthPending({ provider, onCancel }: SignInAuthPend
|
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
30
|
<div className="w-full flex flex-col gap-2">
|
|
31
|
-
<
|
|
31
|
+
<div className="flex justify-center">
|
|
32
32
|
<Spinner />
|
|
33
|
-
|
|
34
|
-
</
|
|
33
|
+
</div>
|
|
34
|
+
<span>{t('auth.pending.authenticating')}</span>
|
|
35
35
|
{onCancel && (
|
|
36
36
|
<SignInStepButton variant="ghost" onClick={onCancel}>
|
|
37
37
|
{t('auth.pending.cancel')}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
3
|
+
import SignInEmailStep from './SignInEmailStep';
|
|
4
|
+
|
|
5
|
+
const resolveTenant = vi.hoisted(() => vi.fn());
|
|
6
|
+
vi.mock('@vertesia/ui/session', () => ({ setFirebaseTenant: resolveTenant }));
|
|
7
|
+
vi.mock('@vertesia/ui/i18n', () => ({ useUITranslation: () => ({ t: (key: string) => key }) }));
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
cleanup();
|
|
10
|
+
vi.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('keeps feedback visible and prevents duplicate requests while tenant discovery is pending', async () => {
|
|
14
|
+
let finish!: () => void;
|
|
15
|
+
resolveTenant.mockReturnValue(
|
|
16
|
+
new Promise<void>((resolve) => {
|
|
17
|
+
finish = resolve;
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
const onProceed = vi.fn();
|
|
21
|
+
render(<SignInEmailStep initialEmail="person@example.com" onProceed={onProceed} />);
|
|
22
|
+
const button = screen.getByRole('button', { name: 'auth.continue' });
|
|
23
|
+
fireEvent.click(button);
|
|
24
|
+
expect(screen.queryByRole('button', { name: 'auth.continue' })).toBeNull();
|
|
25
|
+
expect(screen.getByRole('textbox')).toBeTruthy();
|
|
26
|
+
expect(screen.getByRole('status')).toBeTruthy();
|
|
27
|
+
expect(button.contains(screen.getByRole('status'))).toBe(false);
|
|
28
|
+
fireEvent.submit(screen.getByRole('textbox').closest('form')!);
|
|
29
|
+
expect(resolveTenant).toHaveBeenCalledTimes(1);
|
|
30
|
+
finish();
|
|
31
|
+
await waitFor(() => expect(onProceed).toHaveBeenCalledWith('person@example.com', undefined));
|
|
32
|
+
});
|
|
@@ -67,16 +67,20 @@ export default function SignInEmailStep({ initialEmail, onProceed }: SignInEmail
|
|
|
67
67
|
error={submitError ? t('auth.email.invalidError') : undefined}
|
|
68
68
|
/>
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
{loading ? (
|
|
71
|
+
<div
|
|
72
|
+
role="status"
|
|
73
|
+
aria-label={t('auth.pending.authenticating')}
|
|
74
|
+
className="h-[42px] flex items-center justify-center"
|
|
75
|
+
>
|
|
72
76
|
<Spinner />
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
</div>
|
|
78
|
+
) : (
|
|
79
|
+
<SignInStepButton type="submit">
|
|
80
|
+
<span>{t('auth.continue')}</span>
|
|
81
|
+
<ArrowRight className="!size-3.5" />
|
|
82
|
+
</SignInStepButton>
|
|
83
|
+
)}
|
|
80
84
|
</form>
|
|
81
85
|
</SignInStepLayout>
|
|
82
86
|
);
|