@vertesia/ui 0.79.1 → 0.80.0-dev.20251121

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 (76) hide show
  1. package/lib/esm/core/components/TagsInput.js +194 -0
  2. package/lib/esm/core/components/TagsInput.js.map +1 -0
  3. package/lib/esm/core/components/index.js +2 -1
  4. package/lib/esm/core/components/index.js.map +1 -1
  5. package/lib/esm/core/components/shadcn/popover.js +1 -1
  6. package/lib/esm/core/components/shadcn/popover.js.map +1 -1
  7. package/lib/esm/env/index.js +1 -1
  8. package/lib/esm/env/index.js.map +1 -1
  9. package/lib/esm/features/store/collections/EditCollectionView.js +18 -7
  10. package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
  11. package/lib/esm/features/store/collections/SelectCollection.js +104 -39
  12. package/lib/esm/features/store/collections/SelectCollection.js.map +1 -1
  13. package/lib/esm/features/store/collections/SharedPropsEditor.js +40 -0
  14. package/lib/esm/features/store/collections/SharedPropsEditor.js.map +1 -0
  15. package/lib/esm/features/store/collections/SyncMemberHeadsToggle.js +35 -0
  16. package/lib/esm/features/store/collections/SyncMemberHeadsToggle.js.map +1 -0
  17. package/lib/esm/features/store/collections/index.js +2 -0
  18. package/lib/esm/features/store/collections/index.js.map +1 -1
  19. package/lib/esm/features/store/objects/upload/useSmartFileUploadProcessing.js +10 -9
  20. package/lib/esm/features/store/objects/upload/useSmartFileUploadProcessing.js.map +1 -1
  21. package/lib/esm/session/UserSessionProvider.js +2 -2
  22. package/lib/esm/session/UserSessionProvider.js.map +1 -1
  23. package/lib/esm/shell/apps/AppProjectSelector.js +2 -2
  24. package/lib/esm/shell/apps/AppProjectSelector.js.map +1 -1
  25. package/lib/tsconfig.tsbuildinfo +1 -1
  26. package/lib/types/core/components/TagsInput.d.ts +16 -0
  27. package/lib/types/core/components/TagsInput.d.ts.map +1 -0
  28. package/lib/types/core/components/index.d.ts +2 -1
  29. package/lib/types/core/components/index.d.ts.map +1 -1
  30. package/lib/types/core/components/shadcn/popover.d.ts +7 -0
  31. package/lib/types/core/components/shadcn/popover.d.ts.map +1 -1
  32. package/lib/types/features/store/collections/EditCollectionView.d.ts.map +1 -1
  33. package/lib/types/features/store/collections/SelectCollection.d.ts +2 -1
  34. package/lib/types/features/store/collections/SelectCollection.d.ts.map +1 -1
  35. package/lib/types/features/store/collections/SharedPropsEditor.d.ts +7 -0
  36. package/lib/types/features/store/collections/SharedPropsEditor.d.ts.map +1 -0
  37. package/lib/types/features/store/collections/SyncMemberHeadsToggle.d.ts +7 -0
  38. package/lib/types/features/store/collections/SyncMemberHeadsToggle.d.ts.map +1 -0
  39. package/lib/types/features/store/collections/index.d.ts +2 -0
  40. package/lib/types/features/store/collections/index.d.ts.map +1 -1
  41. package/lib/types/features/store/objects/upload/useSmartFileUploadProcessing.d.ts.map +1 -1
  42. package/lib/vertesia-ui-core.js +1 -1
  43. package/lib/vertesia-ui-core.js.map +1 -1
  44. package/lib/vertesia-ui-env.js +1 -1
  45. package/lib/vertesia-ui-env.js.map +1 -1
  46. package/lib/vertesia-ui-features.js +1 -1
  47. package/lib/vertesia-ui-features.js.map +1 -1
  48. package/lib/vertesia-ui-session.js +1 -1
  49. package/lib/vertesia-ui-session.js.map +1 -1
  50. package/lib/vertesia-ui-shell.js +1 -1
  51. package/lib/vertesia-ui-shell.js.map +1 -1
  52. package/package.json +171 -166
  53. package/src/core/components/SelectBox.tsx +1 -1
  54. package/src/core/components/TagsInput.tsx +388 -0
  55. package/src/core/components/index.ts +2 -1
  56. package/src/core/components/shadcn/popover.tsx +2 -2
  57. package/src/core/hooks/CompositeState.tsx +156 -6
  58. package/src/env/index.ts +5 -4
  59. package/src/features/agent/PayloadBuilder.tsx +92 -65
  60. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +1 -1
  61. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +2 -2
  62. package/src/features/agent/chat/ModernAgentOutput/WorkstreamTabs.tsx +3 -3
  63. package/src/features/store/collections/EditCollectionView.tsx +31 -10
  64. package/src/features/store/collections/SharedPropsEditor.tsx +61 -0
  65. package/src/features/store/collections/SyncMemberHeadsToggle.tsx +48 -0
  66. package/src/features/store/collections/index.ts +3 -1
  67. package/src/features/store/objects/DocumentSearchResults.tsx +11 -2
  68. package/src/features/store/objects/components/useDownloadObject.ts +7 -2
  69. package/src/features/store/objects/upload/useSmartFileUploadProcessing.ts +1 -3
  70. package/src/features/user/UserInfo.tsx +2 -0
  71. package/src/session/UserSession.ts +1 -0
  72. package/src/session/UserSessionProvider.tsx +10 -2
  73. package/src/session/auth/composable.ts +71 -70
  74. package/src/shell/apps/AppProjectSelector.tsx +2 -2
  75. package/src/widgets/form/Form.tsx +1 -1
  76. package/src/widgets/form/ManagedObject.ts +4 -0
@@ -1,29 +1,33 @@
1
1
  import { AsyncExecutionResult, VertesiaClient } from "@vertesia/client";
2
- import { ExecutionEnvironmentRef, Interaction, mergePromptsSchema, PopulatedInteraction, supportsToolUse } from "@vertesia/common";
2
+ import { AgentSearchScope, ExecutionEnvironmentRef, InCodeInteraction, mergeInCodePromptSchemas, supportsToolUse, WorkflowInteractionVars } from "@vertesia/common";
3
3
  import { JSONObject } from "@vertesia/json";
4
4
  import { useUserSession } from "@vertesia/ui/session";
5
5
  import Ajv, { ValidateFunction } from "ajv";
6
6
  import type { JSONSchema4 } from "json-schema";
7
7
  import React, { createContext, useContext, useEffect, useState } from "react";
8
8
 
9
- export interface ConversationWorkflowPayload {
10
- interaction?: Interaction | undefined;
11
- config: {
12
- environment?: ExecutionEnvironmentRef | undefined;
13
- model?: string;
14
- }
15
- data?: JSONObject | undefined,
16
- tool_names: string[],
17
- }
18
9
 
19
- export class PayloadBuilder implements ConversationWorkflowPayload {
10
+ // export interface ConversationWorkflowPayload {
11
+ // config: {
12
+ // environment?: ExecutionEnvironmentRef | undefined;
13
+ // model?: string;
14
+ // }
15
+ // data?: JSONObject | undefined,
16
+ // tool_names: string[],
17
+ // }
18
+
19
+ export class PayloadBuilder {
20
20
  _interactive: boolean = true;
21
21
  _debug_mode: boolean = false;
22
22
  _collection: string | undefined;
23
23
  _start: boolean = false;
24
24
  _preserveRunValues: boolean = false;
25
+ _interaction: InCodeInteraction | undefined;
26
+ _environment: ExecutionEnvironmentRef | undefined;
27
+ _model: string = '';
28
+ _tool_names: string[] = [];
29
+ _data: JSONObject | undefined;
25
30
 
26
- payload: ConversationWorkflowPayload;
27
31
  private _interactionParamsSchema?: JSONSchema4 | null;
28
32
  private _inputValidator?: {
29
33
  validate: ValidateFunction;
@@ -31,12 +35,6 @@ export class PayloadBuilder implements ConversationWorkflowPayload {
31
35
  };
32
36
 
33
37
  constructor(public vertesia: VertesiaClient, public updateState: (data: PayloadBuilder) => void) {
34
- this.payload = {
35
- config: {
36
- model: '',
37
- },
38
- tool_names: [],
39
- }
40
38
  }
41
39
 
42
40
  onStateChanged() {
@@ -47,7 +45,11 @@ export class PayloadBuilder implements ConversationWorkflowPayload {
47
45
  clone() {
48
46
  const builder = new PayloadBuilder(this.vertesia, this.updateState);
49
47
  builder._interactionParamsSchema = this._interactionParamsSchema;
50
- builder.payload = this.payload;
48
+ builder._interaction = this._interaction;
49
+ builder._data = this._data;
50
+ builder._environment = this._environment;
51
+ builder._model = this._model;
52
+ builder._tool_names = [...this._tool_names];
51
53
  builder._interactive = this._interactive;
52
54
  builder._debug_mode = this._debug_mode;
53
55
  builder._inputValidator = this._inputValidator;
@@ -91,31 +93,64 @@ export class PayloadBuilder implements ConversationWorkflowPayload {
91
93
  }
92
94
 
93
95
  get search_scope() {
94
- return this._collection ? "collection" : undefined;
96
+ return this._collection ? AgentSearchScope.Collection : undefined;
97
+ }
98
+
99
+ async restoreConversation(context: WorkflowInteractionVars) {
100
+
101
+ // Handle version-specific interaction resolution
102
+ let interactionRef = context.interaction;
103
+ if (context.version) {
104
+ const objectIdRegex = /^[a-fA-F0-9]{24}$/;
105
+ if (!objectIdRegex.test(interactionRef)) {
106
+ // regex to check if interactionRef is an object id (24 hex characters), only append version if not an object id
107
+ interactionRef = `${interactionRef}@${context.version}`;
108
+ }
109
+ }
110
+
111
+ const inter = await this.vertesia.interactions.catalog.resolve(interactionRef);
112
+ const envId = inter.runtime?.environment || context.config?.environment;
113
+ const model = context.config?.model;
114
+ const env = await (envId ?
115
+ this.vertesia.environments.retrieve(context.config?.environment)
116
+ :
117
+ Promise.resolve(undefined)
118
+ );
119
+
120
+
121
+ this.interactionParamsSchema = context.interactionParamsSchema ?? null;
122
+ // trigger the setter to update the corresponding interactionParamsSchema
123
+ this.interaction = inter;
124
+
125
+ this._tool_names = context.tool_names || [];
126
+ this._data = context.data;
127
+ this._interactive = context.interactive;
128
+ this._debug_mode = context.debug_mode ?? false;
129
+ this.collection = context.collection_id ?? undefined;
130
+
131
+ // we need to trigger the setter to deal with default models
132
+ this.environment = env;
133
+ if (model) {
134
+ this._model = model;
135
+ }
136
+
137
+ this.onStateChanged();
95
138
  }
96
139
 
97
140
  get interaction() {
98
- return this.payload.interaction;
141
+ return this._interaction;
99
142
  }
100
- set interaction(interaction: Interaction | undefined) {
101
- if (interaction?.id !== this.payload.interaction?.id) {
102
- this.payload.interaction = interaction;
103
- this._interactionParamsSchema = mergePromptsSchema(this.interaction as PopulatedInteraction) as JSONSchema4;
143
+ set interaction(interaction: InCodeInteraction | undefined) {
144
+ if (interaction?.id !== this._interaction?.id) {
145
+ this._interaction = interaction;
146
+ // trigger the setter to update the onChange state
147
+ this.interactionParamsSchema = interaction ? mergeInCodePromptSchemas(interaction.prompts) as JSONSchema4 : undefined;
104
148
  // Reset the validator when schema changes
105
149
  this._inputValidator = undefined;
106
150
  if (interaction && !this._preserveRunValues) {
107
- if (interaction.environment) {
108
- if (typeof interaction.environment === 'string') {
109
- this.vertesia.environments.retrieve(interaction.environment).then((environment) => this.environment = environment);
110
- } else {
111
- this.payload.config.environment = interaction.environment;
112
- }
113
- }
114
- if (interaction.model) {
115
- this.payload.config.model = interaction.model;
116
- } else {
117
- this.payload.config.model = this.environment?.default_model && supportsToolUse(this.environment.default_model, this.environment.provider)
118
- ? this.environment.default_model : undefined;
151
+ if (interaction.runtime?.environment) {
152
+ const envId = interaction.runtime.environment;
153
+ this.vertesia.environments.retrieve(envId).then((environment) => this.environment = environment);
119
154
  }
120
155
  }
121
156
  this.onStateChanged();
@@ -123,19 +158,19 @@ export class PayloadBuilder implements ConversationWorkflowPayload {
123
158
  }
124
159
 
125
160
  get environment() {
126
- return this.payload.config.environment;
161
+ return this._environment;
127
162
  }
128
163
  set environment(environment: ExecutionEnvironmentRef | undefined) {
129
- if (environment?.id !== this.payload.config.environment?.id) {
130
- this.payload.config.environment = environment;
164
+ if (environment?.id !== this._environment?.id) {
165
+ this._environment = environment;
131
166
  if (!this._preserveRunValues) {
132
167
  // First try to use the interaction model, then the environment default model
133
- const interactionModel = this.payload.interaction?.model;
168
+ const interactionModel = this.interaction?.runtime?.model;
134
169
  if (interactionModel && environment && supportsToolUse(interactionModel, environment.provider)) {
135
- this.payload.config.model = interactionModel;
170
+ this._model = interactionModel;
136
171
  } else {
137
- this.payload.config.model = environment?.default_model && supportsToolUse(environment.default_model, environment.provider)
138
- ? environment.default_model : undefined;
172
+ this._model = environment?.default_model && supportsToolUse(environment.default_model, environment.provider)
173
+ ? environment.default_model : '';
139
174
  }
140
175
  }
141
176
 
@@ -144,35 +179,31 @@ export class PayloadBuilder implements ConversationWorkflowPayload {
144
179
  }
145
180
 
146
181
  get model() {
147
- return this.payload.config.model;
182
+ return this._model;
148
183
  }
149
184
  set model(model: string | undefined) {
150
- if (model !== this.payload.config.model) {
151
- this.payload.config.model = model;
185
+ if (model !== this._model) {
186
+ this._model = model || '';
152
187
  this.onStateChanged();
153
188
  }
154
189
  }
155
190
 
156
191
  get tool_names() {
157
- return this.payload.tool_names;
192
+ return this._tool_names;
158
193
  }
159
194
  set tool_names(tools: string[]) {
160
- this.payload.tool_names = tools;
195
+ this._tool_names = tools;
161
196
  this.onStateChanged();
162
197
  }
163
198
 
164
199
  get data(): JSONObject | undefined {
165
- return this.payload.data;
200
+ return this._data;
166
201
  }
167
202
  set data(prompt_data: JSONObject) {
168
- this.payload.data = prompt_data;
203
+ this._data = prompt_data;
169
204
  this.onStateChanged();
170
205
  }
171
206
 
172
- get config() {
173
- return this.payload.config;
174
- }
175
-
176
207
  set run(run: AsyncExecutionResult | { workflow_id: string; run_id: string }) {
177
208
  console.log("run", run);
178
209
  this.onStateChanged();
@@ -214,15 +245,11 @@ export class PayloadBuilder implements ConversationWorkflowPayload {
214
245
  this._debug_mode = false;
215
246
  this._collection = undefined;
216
247
  this._preserveRunValues = false;
217
- this.payload = {
218
- config: {
219
- environment: undefined,
220
- model: '',
221
- },
222
- tool_names: [],
223
- interaction: undefined,
224
- data: undefined
225
- };
248
+ this._model = '';
249
+ this._environment = undefined;
250
+ this._tool_names = [];
251
+ this._interaction = undefined;
252
+ this._data = undefined;
226
253
  this._interactionParamsSchema = null;
227
254
  this._inputValidator = undefined;
228
255
  this.model = undefined;
@@ -250,7 +277,7 @@ export class PayloadBuilder implements ConversationWorkflowPayload {
250
277
  };
251
278
  }
252
279
 
253
- const prompt_data = this.payload.data || {};
280
+ const prompt_data = this._data || {};
254
281
  const isValid = this._inputValidator.validate(prompt_data);
255
282
 
256
283
  if (!isValid) {
@@ -119,7 +119,7 @@ export default function AllMessagesMixed({
119
119
  return (
120
120
  <div
121
121
  ref={containerRef}
122
- className="flex-1 min-h-0 h-full overflow-y-auto py-2 px-4 sm:px-6 lg:px-8 flex flex-col relative"
122
+ className="flex-1 min-h-0 h-full overflow-y-auto px-4 sm:px-2 lg:px-4 flex flex-col relative"
123
123
  data-testid="all-messages-mixed"
124
124
  >
125
125
 
@@ -37,7 +37,7 @@ export default function Header({
37
37
  }: HeaderProps) {
38
38
  return (
39
39
  <PayloadBuilderProvider>
40
- <div className="flex items-center justify-between py-1.5 px-2 border-b shadow-sm flex-shrink-0">
40
+ <div className="flex flex-wrap items-end justify-between py-1.5 px-2 border-b shadow-sm flex-shrink-0">
41
41
  <div className="flex flex-wrap items-center space-x-2">
42
42
  <div className="flex items-center space-x-1">
43
43
  <Bot className="size-5 text-muted" />
@@ -47,7 +47,7 @@ export default function Header({
47
47
  (Run ID: {run.runId.substring(0, 8)}...)
48
48
  </span>
49
49
  </div>
50
- <div className="flex items-center space-x-2">
50
+ <div className="flex justify-end items-center space-x-2 ml-auto">
51
51
  {/* View Mode Toggle */}
52
52
  <div className="flex items-center space-x-1 bg-muted rounded p-0.5">
53
53
  <Button variant={viewMode === "stacked" ? "outline" : "ghost"} size="xs" className="rounded-l-md" onClick={() => onViewModeChange("stacked")}>
@@ -62,11 +62,11 @@ export default function WorkstreamTabs({
62
62
  }
63
63
 
64
64
  return (
65
- <div className="flex overflow-x-auto space-x-1 mb-2 border-b-2 border-muted/20 sticky top-0 z-10 pt-1">
65
+ <div className="flex overflow-x-auto space-x-1 mb-2 bg-muted border-b-2 border-muted/20 sticky top-0 z-10">
66
66
  {sortedWorkstreams.map(([id, name]) => (
67
67
  <button
68
68
  key={id}
69
- className={`px-3 py-1.5 text-xs font-medium whitespace-nowrap transition-colors flex items-center gap-1.5
69
+ className={`px-2 py-1 text-xs font-medium whitespace-nowrap transition-colors flex items-center gap-1.5
70
70
  ${activeWorkstream === id
71
71
  ? "bg-info text-info border-b-2 border-info"
72
72
  : "text-muted hover:bg-muted border-b-2 border-transparent"
@@ -79,7 +79,7 @@ export default function WorkstreamTabs({
79
79
  {count && count.has(id) && count.get(id)! > 0 && (
80
80
  <div className="flex items-center space-x-1">
81
81
  <span
82
- className={`inline-flex items-center justify-center size-4 text-xs rounded-full
82
+ className={`inline-flex items-center justify-center p-1 text-xs rounded-full
83
83
  ${activeWorkstream === id
84
84
  ? "bg-info text-info"
85
85
  : "bg-muted text-muted"
@@ -1,12 +1,14 @@
1
1
  import { json } from "@codemirror/lang-json";
2
- import dayjs from "dayjs";
3
- import { basicSetup } from "codemirror";
4
- import { useMemo, useRef, useState } from "react";
2
+ import { Collection, CreateCollectionPayload, JSONSchemaObject } from "@vertesia/common";
3
+ import { Button, ErrorBox, FormItem, Input, Panel, Styles, Textarea, useFetch, useToast } from "@vertesia/ui/core";
5
4
  import { UserInfo } from "@vertesia/ui/features";
5
+ import { SharedPropsEditor } from "@vertesia/ui/features";
6
+ import { SyncMemberHeadsToggle } from "@vertesia/ui/features";
6
7
  import { useUserSession } from "@vertesia/ui/session";
7
- import { Collection, CreateCollectionPayload } from "@vertesia/common";
8
- import { CodeMirrorEditor, EditorApi, GeneratedForm, ManagedObject } from "@vertesia/ui/widgets";
9
- import { Button, ErrorBox, FormItem, Input, Styles, Textarea, useFetch, useToast, Panel } from "@vertesia/ui/core";
8
+ import { CodeMirrorEditor, EditorApi, GeneratedForm, ManagedObject, Node } from "@vertesia/ui/widgets";
9
+ import { basicSetup } from "codemirror";
10
+ import dayjs from "dayjs";
11
+ import { useMemo, useRef, useState } from "react";
10
12
  import { SelectContentType, stringifyTableLayout } from "../types";
11
13
 
12
14
  const extensions = [basicSetup, json()];
@@ -211,7 +213,16 @@ export function EditCollectionView({ refetch, collection }: EditCollectionViewPr
211
213
  </Panel>
212
214
 
213
215
  {typeId && <PropertiesEditor typeId={typeId} collection={collection} />}
214
- </div>
216
+ {
217
+ !collection.dynamic && (
218
+ <>
219
+ <SyncMemberHeadsToggle collection={collection} />
220
+ <SharedPropsEditor collection={collection} />
221
+ </>
222
+ )
223
+ }
224
+
225
+ </div >
215
226
  );
216
227
  }
217
228
 
@@ -220,6 +231,7 @@ interface PropertiesEditorProps {
220
231
  collection: Collection;
221
232
  }
222
233
  function PropertiesEditor({ typeId, collection }: PropertiesEditorProps) {
234
+ const [formData, setFormData] = useState<JSONSchemaObject>({});
223
235
  const toast = useToast();
224
236
  const { client } = useUserSession();
225
237
  const [isUpdating, setIsUpdating] = useState(false);
@@ -237,7 +249,10 @@ function PropertiesEditor({ typeId, collection }: PropertiesEditorProps) {
237
249
  }
238
250
 
239
251
 
240
- const _onSave = (data: any) => {
252
+ const _onSave = (data: JSONSchemaObject) => {
253
+ if (!data || !Object.keys(data).length) {
254
+ return;
255
+ }
241
256
  const payload = { properties: data || {} };
242
257
  setIsUpdating(true);
243
258
  client.store.collections
@@ -263,13 +278,19 @@ function PropertiesEditor({ typeId, collection }: PropertiesEditorProps) {
263
278
  });
264
279
  };
265
280
 
281
+ const onDataChanged = (data: Node) => {
282
+ if (data instanceof ManagedObject) {
283
+ setFormData(data.value);
284
+ }
285
+ }
286
+
266
287
  return (
267
288
  <Panel title="Properties" action={
268
- <Button size="lg" isLoading={isUpdating} type="submit">
289
+ <Button size="lg" isLoading={isUpdating} type="submit" onClick={() => _onSave(formData)}>
269
290
  Save
270
291
  </Button>}
271
292
  >
272
- <GeneratedForm object={object} onSubmit={_onSave} />
293
+ <GeneratedForm object={object} onChange={onDataChanged} />
273
294
  </Panel>
274
295
  );
275
296
  }
@@ -0,0 +1,61 @@
1
+ import { Collection, ContentObjectType } from "@vertesia/common";
2
+ import { Button, Panel, useToast } from "@vertesia/ui/core";
3
+ import { TagsInput } from "@vertesia/ui/core";
4
+ import { useUserSession } from "@vertesia/ui/session";
5
+ import { useEffect, useState } from "react";
6
+
7
+ interface SharedPropsEditorProps {
8
+ collection: Collection;
9
+ }
10
+ export function SharedPropsEditor({ collection }: SharedPropsEditorProps) {
11
+
12
+ const { client } = useUserSession();
13
+ const [colType, setColType] = useState<ContentObjectType | undefined>(undefined);
14
+ const [sharedProps, setSharedProps] = useState<string[]>(collection.shared_properties || []);
15
+ const toast = useToast();
16
+
17
+ useEffect(() => {
18
+ if (collection.type?.id) {
19
+ client.store.types.retrieve(collection.type.id).then(setColType);
20
+ }
21
+ }, [collection.type?.id]);
22
+
23
+ const options: string[] = colType ? Object.keys(colType.object_schema?.properties || {}) : [];
24
+
25
+ const onSelect = (selected: string[]) => {
26
+ setSharedProps(selected);
27
+ }
28
+
29
+ const onSave = () => {
30
+ client.store.collections.update(collection.id, {
31
+ shared_properties: sharedProps
32
+ }).then(() => {
33
+ // Handle success
34
+ toast({
35
+ title: "Updated shared properties",
36
+ status: "success"
37
+ })
38
+ }).catch((error) => {
39
+ toast({
40
+ title: "Failed to update shared properties",
41
+ description: error.message,
42
+ status: "error"
43
+ })
44
+ // Handle error
45
+ });
46
+ }
47
+
48
+ return (
49
+ <Panel title="Shared Properties" description="Add properties to share across all members in the collection. This feature requires to enable shared property synchronization on the project."
50
+ action={
51
+ <Button size="lg" isLoading={false} onClick={onSave}>
52
+ Save
53
+ </Button>}
54
+ >
55
+ <div className=''>
56
+ <TagsInput value={sharedProps} onChange={onSelect} options={options} placeholder="Select properties to share" />
57
+ </div>
58
+ </Panel>
59
+ )
60
+
61
+ }
@@ -0,0 +1,48 @@
1
+ import { Collection } from "@vertesia/common";
2
+ import { Panel, Switch, useToast } from "@vertesia/ui/core";
3
+ import { useUserSession } from "@vertesia/ui/session";
4
+ import { useState } from "react";
5
+
6
+ interface SyncMemberHeadsToggleProps {
7
+ collection: Collection;
8
+ }
9
+ export function SyncMemberHeadsToggle({ collection }: SyncMemberHeadsToggleProps) {
10
+
11
+ const { client } = useUserSession();
12
+ const [skipHeadSync, setSkipHeadSync] = useState<boolean>(collection.skip_head_sync ?? false);
13
+ const [isSaving, setIsSaving] = useState<boolean>(false);
14
+ const toast = useToast();
15
+
16
+ const onSaveSkipHeadSync = (enableSyncHeads: boolean) => {
17
+ const skip_head_sync = !enableSyncHeads;
18
+ setIsSaving(true);
19
+ client.store.collections.update(collection.id, {
20
+ skip_head_sync: skip_head_sync
21
+ }).then(() => {
22
+ // Handle success
23
+ toast({
24
+ title: "Updated skip head sync setting",
25
+ status: "success"
26
+ })
27
+ setSkipHeadSync(skip_head_sync);
28
+ }).catch((error) => {
29
+ toast({
30
+ title: "Failed to update skip head sync",
31
+ description: error.message,
32
+ status: "error"
33
+ })
34
+ // Handle error
35
+ }).finally(() => {
36
+ setIsSaving(false);
37
+ });
38
+ }
39
+
40
+ return (
41
+ <Panel title="Synchronize Member Heads" description="When a new HEAD version of a member is created the colleciton will be updated to point to the new HEAD.">
42
+ <Switch disabled={isSaving} value={!skipHeadSync} onChange={onSaveSkipHeadSync}>
43
+ Enable synchronizing member heads
44
+ </Switch>
45
+ </Panel>
46
+ )
47
+
48
+ }
@@ -2,4 +2,6 @@ export * from "./BrowseCollectionView";
2
2
  export * from "./CollectionsTable";
3
3
  export * from "./EditCollectionView";
4
4
  export * from "./CreateCollection";
5
- export * from "./SelectCollection";
5
+ export * from "./SelectCollection";
6
+ export * from "./SharedPropsEditor";
7
+ export * from "./SyncMemberHeadsToggle";
@@ -27,7 +27,13 @@ const defaultLayout: ColumnLayout[] = [
27
27
 
28
28
  function getTableLayout(registry: TypeRegistry, type: string | undefined): ColumnLayout[] {
29
29
  const layout = type ? registry.getTypeLayout(type) : defaultLayout;
30
- return layout ?? defaultLayout;
30
+ console.log('[DEBUG] getTableLayout called with type:', type);
31
+ console.log('[DEBUG] Layout from registry:', layout);
32
+ console.log('[DEBUG] Using defaultLayout?', layout === defaultLayout);
33
+ const result = layout ?? defaultLayout;
34
+ console.log('[DEBUG] Final layout:', result);
35
+ console.log('[DEBUG] Has Status field?', result.some(col => col.field === 'status'));
36
+ return result;
31
37
  }
32
38
 
33
39
  interface DocumentSearchResultsWithDropZoneProps {
@@ -94,6 +100,9 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
94
100
  const [actualLayout, setActualLayout] = useState<ColumnLayout[]>(
95
101
  typeRegistry ? layout || getTableLayout(typeRegistry, search.query.type) : defaultLayout,
96
102
  );
103
+
104
+ console.log('[DEBUG] DocumentSearchResults - actualLayout:', actualLayout);
105
+ console.log('[DEBUG] DocumentSearchResults - actualLayout has Status?', actualLayout.some(col => col.field === 'status'));
97
106
  //TODO _setRefreshTrigger state not used
98
107
  const [refreshTrigger, _setRefreshTrigger] = useState(0);
99
108
  const [loaded, setLoaded] = useState(0);
@@ -344,7 +353,7 @@ function OverviewDrawer({ object, onClose }: OverviewDrawerProps) {
344
353
  const { store } = useUserSession();
345
354
  const toast = useToast();
346
355
  const navigate = useNavigate();
347
- const onDownload = useDownloadDocument(store, toast, object?.content?.source);
356
+ const onDownload = useDownloadDocument(store, toast, object?.content?.source, object?.name || object?.content?.name);
348
357
 
349
358
  return object ? (
350
359
  <SidePanel title={object.properties?.title || object.name} isOpen={true} onClose={onClose}>
@@ -1,11 +1,16 @@
1
1
  import { VertesiaClient, ZenoClient } from "@vertesia/client";
2
2
  import { ToastFn } from "@vertesia/ui/core";
3
3
 
4
- export function useDownloadDocument(client: VertesiaClient | ZenoClient, toast: ToastFn, uri: string | undefined) {
4
+ export function useDownloadDocument(
5
+ client: VertesiaClient | ZenoClient,
6
+ toast: ToastFn,
7
+ uri: string | undefined,
8
+ name?: string,
9
+ ) {
5
10
  const onDownload = () => {
6
11
  if (uri) {
7
12
  client.files
8
- .getDownloadUrl(uri)
13
+ .getDownloadUrlWithOptions({ file: uri, name })
9
14
  .then((r) => {
10
15
  window.open(r.url, "_blank");
11
16
  })
@@ -181,9 +181,7 @@ export function useSmartFileUploadProcessing() {
181
181
  queries.push(res);
182
182
  } else {
183
183
  const res = client.store.objects.find({
184
- query: {
185
- match: query,
186
- },
184
+ query: query,
187
185
  select: "id content.name location" // Only fetch fields needed for comparison
188
186
  });
189
187
  queries.push(res);
@@ -125,6 +125,8 @@ export function UserInfo({ userRef, showTitle = false, size = "md" }: UserInfoPr
125
125
  return <SystemAvatar showTitle={showTitle} size={size} />
126
126
  case PrincipalType.ServiceAccount:
127
127
  return <ServiceAccountAvatar accountId={id} showTitle={showTitle} size={size} />
128
+ case PrincipalType.Agent:
129
+ return <ServiceAccountAvatar accountId={id} showTitle={showTitle} size={size} />
128
130
  case PrincipalType.ApiKey:
129
131
  return <ApiKeyAvatar keyId={id} size={size} showTitle={showTitle} />
130
132
  default:
@@ -33,6 +33,7 @@ class UserSession {
33
33
  this.client = new VertesiaClient({
34
34
  serverUrl: Env.endpoints.studio,
35
35
  storeUrl: Env.endpoints.zeno,
36
+ tokenServerUrl: Env.endpoints.sts
36
37
  });
37
38
  }
38
39
 
@@ -1,5 +1,5 @@
1
1
  import { onAuthStateChanged } from "firebase/auth";
2
- import { ReactNode, useEffect, useState } from "react";
2
+ import { ReactNode, useEffect, useRef, useState } from "react";
3
3
  import { UserNotFoundError, getComposableToken } from "./auth/composable";
4
4
  import { getFirebaseAuth } from "./auth/firebase";
5
5
  import { useAuthState } from "./auth/useAuthState";
@@ -27,9 +27,10 @@ export function UserSessionProvider({ children }: UserSessionProviderProps) {
27
27
  const state = hashParams.get("state");
28
28
  const [session, setSession] = useState<UserSession>(new UserSession());
29
29
  const { generateState, verifyState, clearState } = useAuthState();
30
+ const hasInitiatedAuthRef = useRef(false);
30
31
 
31
32
  const redirectToCentralAuth = (projectId?: string, accountId?: string) => {
32
- const url = new URL(CENTRAL_AUTH_REDIRECT);
33
+ const url = new URL(`${CENTRAL_AUTH_REDIRECT}?sts=${Env.endpoints.sts ?? "https://sts.vertesia.io"}`);
33
34
  const currentUrl = new URL(window.location.href);
34
35
  currentUrl.hash = "";
35
36
  if (projectId) currentUrl.searchParams.set("p", projectId);
@@ -40,6 +41,13 @@ export function UserSessionProvider({ children }: UserSessionProviderProps) {
40
41
  };
41
42
 
42
43
  useEffect(() => {
44
+ // Make this effect idempotent - only run auth flow once
45
+ if (hasInitiatedAuthRef.current) {
46
+ console.log("Auth: skipping duplicate auth flow initiation");
47
+ return;
48
+ }
49
+ hasInitiatedAuthRef.current = true;
50
+
43
51
  console.log("Auth: starting auth flow");
44
52
  Env.logger.info("Starting auth flow");
45
53
  const currentUrl = new URL(window.location.href);