Compare commits

..
Author SHA1 Message Date
claude[bot]andFélix Malfait 2272e4f247 Remove unrelated SettingsAdminWorkspaceChatThread from this PR
This file was added in the base branch but is unrelated to the
impersonation refactoring. It should be submitted in a separate PR.

Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com>
2026-04-12 12:28:16 +00:00
Claude f33973bc24 Add try-catch around JSON.parse in stopImpersonating
If sessionStorage contains corrupted data, fall back to signOut()
instead of leaving the user stuck in the impersonated session.

https://claude.ai/code/session_01SdHcodsLnF7R6Zg8fUtKj8
2026-04-12 12:22:58 +00:00
Claude 777a031fd2 Refactor impersonation to use token restoration instead of session destruction
Replace useImpersonationAuth with useImpersonationSession that stores the
admin's original tokens in sessionStorage before swapping to the impersonated
user's session. "Stop Impersonating" now restores the original tokens and
navigates back instead of signing out completely.

- Same-workspace impersonation (admin panel + Members page): in-place token
  swap with Apollo cache reset, restorable via "Stop Impersonating"
- Cross-workspace impersonation (admin panel): still opens new tab via redirect
- Stop Impersonating: restores original session without re-login, falls back
  to window.close() or signOut() for cross-workspace tabs

https://claude.ai/code/session_01SdHcodsLnF7R6Zg8fUtKj8
2026-04-12 12:20:06 +00:00
Claude 06d8719e07 Fix lint errors in SettingsAdminWorkspaceChatThread
- Merge duplicate 'twenty-ui/layout' imports (Card and Section)
- Use explicit null check instead of truthiness for toolName filter

https://claude.ai/code/session_01SdHcodsLnF7R6Zg8fUtKj8
2026-04-12 12:19:13 +00:00
Claude 91b0937315 Fix CI lint/typecheck failures and admin panel UI issues
- Remove undefined useNavigate reference in SettingsAdminWorkspaceDetail
  (was crashing at runtime since useNavigate was not imported)
- Replace onCompleted callback with useEffect for Apollo useQuery
  compatibility, fix DeepPartialObject type mismatch
- Replace onClick+navigate with to prop on TableRow in SettingsAdminAI
  for accessible link behavior (cmd+click to open in new tab)
- Right-align Workspace column in Recent Users table
- Fix prettier formatting across changed files

https://claude.ai/code/session_01SdHcodsLnF7R6Zg8fUtKj8
2026-04-12 06:27:48 +00:00
Félix MalfaitandClaude Opus 4.6 c30d4ea8f5 Fix admin-panel test missing repository mocks and lint issues
- Add WorkspaceEntity, UserWorkspaceEntity, FeatureFlagEntity,
  AgentChatThreadEntity, AgentMessageEntity repository mocks to
  admin-panel.service.spec.ts
- Replace navigate() with TableRow `to` prop in workspace detail chats tab
- Fix prettier formatting in agent-chat.service.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:35:47 +02:00
Félix MalfaitandGitHub c5c77bed7d Merge branch 'main' into claude/recursing-turing 2026-04-11 21:30:07 +02:00
Félix MalfaitandClaude Opus 4.6 00e6f55c8d Evolve admin panel: dashboard tables, user detail page, members tab, chat fixes
- Add recent users and top workspaces tables to admin panel General tab with fuzzy search
- Add user detail page with user info, workspace tabs, and impersonate button
- Add members tab on workspace detail page with impersonate per user
- Move feature flags to dedicated tab on workspace detail page
- Add creation date and activation status to user/workspace info cards
- Fix chat thread viewer to show assistant messages (query by threadId only)
- Use LazyMarkdownRenderer for assistant messages in chat thread viewer
- Fix root cause of assistant messages having null workspaceId by using
  Repository.insert() instead of .create()/.save() to bypass TypeORM
  @ManyToOne/@JoinColumn column resolution issue

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 16:07:57 +02:00
Félix MalfaitandGitHub bbf875d21d Merge branch 'main' into claude/recursing-turing 2026-04-11 11:25:14 +02:00
Félix MalfaitandClaude Opus 4.6 99da104d94 Add admin panel workspace detail page with chat thread viewer
Introduce a workspace detail page accessible from the AI usage table
in the admin panel. The page has two tabs: Info (reusing existing
SettingsAdminWorkspaceContent) and Chats (listing AI chat threads,
gated by workspace allowImpersonation flag). Chat threads are clickable
to view the full conversation in a read-only message view.

Backend: new GraphQL queries (workspaceLookupAdminPanel,
getAdminWorkspaceChatThreads, getAdminChatThreadMessages) with
business logic in AdminPanelService and security checks via
assertWorkspaceAllowsImpersonation helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:53:54 +02:00
125 changed files with 2003 additions and 1591 deletions
@@ -70,6 +70,27 @@ export type AdminAiModels = {
models: Array<AdminAiModelConfig>;
};
export type AdminChatMessage = {
__typename?: 'AdminChatMessage';
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
parts: Array<AdminChatMessagePart>;
role: Scalars['String'];
};
export type AdminChatMessagePart = {
__typename?: 'AdminChatMessagePart';
textContent?: Maybe<Scalars['String']>;
toolName?: Maybe<Scalars['String']>;
type: Scalars['String'];
};
export type AdminChatThreadMessages = {
__typename?: 'AdminChatThreadMessages';
messages: Array<AdminChatMessage>;
thread: AdminWorkspaceChatThread;
};
export type AdminPanelHealthServiceData = {
__typename?: 'AdminPanelHealthServiceData';
description: Scalars['String'];
@@ -86,6 +107,25 @@ export enum AdminPanelHealthServiceStatus {
OUTAGE = 'OUTAGE'
}
export type AdminPanelRecentUser = {
__typename?: 'AdminPanelRecentUser';
createdAt: Scalars['DateTime'];
email: Scalars['String'];
firstName?: Maybe<Scalars['String']>;
id: Scalars['UUID'];
lastName?: Maybe<Scalars['String']>;
workspaceId?: Maybe<Scalars['UUID']>;
workspaceName?: Maybe<Scalars['String']>;
};
export type AdminPanelTopWorkspace = {
__typename?: 'AdminPanelTopWorkspace';
id: Scalars['UUID'];
name: Scalars['String'];
subdomain: Scalars['String'];
totalUsers: Scalars['Int'];
};
export type AdminPanelWorkerQueueHealth = {
__typename?: 'AdminPanelWorkerQueueHealth';
id: Scalars['String'];
@@ -93,6 +133,17 @@ export type AdminPanelWorkerQueueHealth = {
status: AdminPanelHealthServiceStatus;
};
export type AdminWorkspaceChatThread = {
__typename?: 'AdminWorkspaceChatThread';
conversationSize: Scalars['Int'];
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
title?: Maybe<Scalars['String']>;
totalInputTokens: Scalars['Int'];
totalOutputTokens: Scalars['Int'];
updatedAt: Scalars['DateTime'];
};
export type Agent = {
__typename?: 'Agent';
applicationId?: Maybe<Scalars['UUID']>;
@@ -4314,6 +4365,8 @@ export type PublicWorkspaceData = {
export type Query = {
__typename?: 'Query';
adminPanelRecentUsers: Array<AdminPanelRecentUser>;
adminPanelTopWorkspaces: Array<AdminPanelTopWorkspace>;
agentTurns: Array<AgentTurn>;
apiKey?: Maybe<ApiKey>;
apiKeys: Array<ApiKey>;
@@ -4362,6 +4415,8 @@ export type Query = {
getAddressDetails: PlaceDetailsResult;
getAdminAiModels: AdminAiModels;
getAdminAiUsageByWorkspace: Array<UsageBreakdownItem>;
getAdminChatThreadMessages: AdminChatThreadMessages;
getAdminWorkspaceChatThreads: Array<AdminWorkspaceChatThread>;
getAiProviders: Scalars['JSON'];
getApprovedAccessDomains: Array<ApprovedAccessDomain>;
getAutoCompleteAddress: Array<AutocompleteResult>;
@@ -4427,6 +4482,17 @@ export type Query = {
versionInfo: VersionInfo;
webhook?: Maybe<Webhook>;
webhooks: Array<Webhook>;
workspaceLookupAdminPanel: UserLookup;
};
export type QueryAdminPanelRecentUsersArgs = {
searchTerm?: InputMaybe<Scalars['String']>;
};
export type QueryAdminPanelTopWorkspacesArgs = {
searchTerm?: InputMaybe<Scalars['String']>;
};
@@ -4592,6 +4658,17 @@ export type QueryGetAdminAiUsageByWorkspaceArgs = {
};
export type QueryGetAdminChatThreadMessagesArgs = {
threadId: Scalars['String'];
workspaceId: Scalars['String'];
};
export type QueryGetAdminWorkspaceChatThreadsArgs = {
workspaceId: Scalars['String'];
};
export type QueryGetAutoCompleteAddressArgs = {
address: Scalars['String'];
country?: InputMaybe<Scalars['String']>;
@@ -4827,6 +4904,11 @@ export type QueryWebhookArgs = {
id: Scalars['UUID'];
};
export type QueryWorkspaceLookupAdminPanelArgs = {
workspaceId: Scalars['String'];
};
export type QueueJob = {
__typename?: 'QueueJob';
attemptsMade: Scalars['Float'];
@@ -5850,6 +5932,7 @@ export type User = {
export type UserInfo = {
__typename?: 'UserInfo';
createdAt: Scalars['DateTime'];
email: Scalars['String'];
firstName?: Maybe<Scalars['String']>;
id: Scalars['UUID'];
@@ -6258,7 +6341,9 @@ export enum WorkspaceActivationStatus {
export type WorkspaceInfo = {
__typename?: 'WorkspaceInfo';
activationStatus: WorkspaceActivationStatus;
allowImpersonation: Scalars['Boolean'];
createdAt: Scalars['DateTime'];
featureFlags: Array<FeatureFlag>;
id: Scalars['UUID'];
logo?: Maybe<Scalars['String']>;
@@ -7381,13 +7466,49 @@ export type UserLookupAdminPanelMutationVariables = Exact<{
}>;
export type UserLookupAdminPanelMutation = { __typename?: 'Mutation', userLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, logo?: string | null, totalUsers: number, allowImpersonation: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', customUrl?: string | null, subdomainUrl: string }, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } };
export type UserLookupAdminPanelMutation = { __typename?: 'Mutation', userLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null, createdAt: string }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, logo?: string | null, totalUsers: number, activationStatus: WorkspaceActivationStatus, createdAt: string, allowImpersonation: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', customUrl?: string | null, subdomainUrl: string }, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } };
export type AdminPanelRecentUsersQueryVariables = Exact<{
searchTerm?: InputMaybe<Scalars['String']>;
}>;
export type AdminPanelRecentUsersQuery = { __typename?: 'Query', adminPanelRecentUsers: Array<{ __typename?: 'AdminPanelRecentUser', id: string, email: string, firstName?: string | null, lastName?: string | null, createdAt: string, workspaceName?: string | null, workspaceId?: string | null }> };
export type AdminPanelTopWorkspacesQueryVariables = Exact<{
searchTerm?: InputMaybe<Scalars['String']>;
}>;
export type AdminPanelTopWorkspacesQuery = { __typename?: 'Query', adminPanelTopWorkspaces: Array<{ __typename?: 'AdminPanelTopWorkspace', id: string, name: string, totalUsers: number, subdomain: string }> };
export type GetAdminChatThreadMessagesQueryVariables = Exact<{
workspaceId: Scalars['String'];
threadId: Scalars['String'];
}>;
export type GetAdminChatThreadMessagesQuery = { __typename?: 'Query', getAdminChatThreadMessages: { __typename?: 'AdminChatThreadMessages', thread: { __typename?: 'AdminWorkspaceChatThread', id: string, title?: string | null, totalInputTokens: number, totalOutputTokens: number, conversationSize: number, createdAt: string, updatedAt: string }, messages: Array<{ __typename?: 'AdminChatMessage', id: string, role: string, createdAt: string, parts: Array<{ __typename?: 'AdminChatMessagePart', type: string, textContent?: string | null, toolName?: string | null }> }> } };
export type GetAdminWorkspaceChatThreadsQueryVariables = Exact<{
workspaceId: Scalars['String'];
}>;
export type GetAdminWorkspaceChatThreadsQuery = { __typename?: 'Query', getAdminWorkspaceChatThreads: Array<{ __typename?: 'AdminWorkspaceChatThread', id: string, title?: string | null, totalInputTokens: number, totalOutputTokens: number, conversationSize: number, createdAt: string, updatedAt: string }> };
export type GetVersionInfoQueryVariables = Exact<{ [key: string]: never; }>;
export type GetVersionInfoQuery = { __typename?: 'Query', versionInfo: { __typename?: 'VersionInfo', currentVersion?: string | null, latestVersion: string } };
export type WorkspaceLookupAdminPanelQueryVariables = Exact<{
workspaceId: Scalars['String'];
}>;
export type WorkspaceLookupAdminPanelQuery = { __typename?: 'Query', workspaceLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null, createdAt: string }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, allowImpersonation: boolean, logo?: string | null, totalUsers: number, activationStatus: WorkspaceActivationStatus, createdAt: string, workspaceUrls: { __typename?: 'WorkspaceUrls', customUrl?: string | null, subdomainUrl: string }, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } };
export type DeleteJobsMutationVariables = Exact<{
queueName: Scalars['String'];
jobIds: Array<Scalars['String']> | Scalars['String'];
@@ -8562,8 +8683,13 @@ export const UpdateDatabaseConfigVariableDocument = {"kind":"Document","definiti
export const GetConfigVariablesGroupedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetConfigVariablesGrouped"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getConfigVariablesGrouped"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"groups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isHiddenOnLoad"}},{"kind":"Field","name":{"kind":"Name","value":"variables"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"isSensitive"}},{"kind":"Field","name":{"kind":"Name","value":"isEnvOnly"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}}]}}]}}]} as unknown as DocumentNode<GetConfigVariablesGroupedQuery, GetConfigVariablesGroupedQueryVariables>;
export const GetDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"isSensitive"}},{"kind":"Field","name":{"kind":"Name","value":"isEnvOnly"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}}]} as unknown as DocumentNode<GetDatabaseConfigVariableQuery, GetDatabaseConfigVariableQueryVariables>;
export const UpdateWorkspaceFeatureFlagDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceFeatureFlag"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkspaceFeatureFlag"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"featureFlag"},"value":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>;
export const UserLookupAdminPanelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UserLookupAdminPanel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userIdentifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userLookupAdminPanel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userIdentifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userIdentifier"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"allowImpersonation"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customUrl"}},{"kind":"Field","name":{"kind":"Name","value":"subdomainUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featureFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UserLookupAdminPanelMutation, UserLookupAdminPanelMutationVariables>;
export const UserLookupAdminPanelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UserLookupAdminPanel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userIdentifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userLookupAdminPanel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userIdentifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userIdentifier"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"activationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"allowImpersonation"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customUrl"}},{"kind":"Field","name":{"kind":"Name","value":"subdomainUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featureFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UserLookupAdminPanelMutation, UserLookupAdminPanelMutationVariables>;
export const AdminPanelRecentUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelRecentUsers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelRecentUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}}]}}]}}]} as unknown as DocumentNode<AdminPanelRecentUsersQuery, AdminPanelRecentUsersQueryVariables>;
export const AdminPanelTopWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelTopWorkspaces"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelTopWorkspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"subdomain"}}]}}]}}]} as unknown as DocumentNode<AdminPanelTopWorkspacesQuery, AdminPanelTopWorkspacesQueryVariables>;
export const GetAdminChatThreadMessagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminChatThreadMessages"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminChatThreadMessages"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"messages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"parts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"toolName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<GetAdminChatThreadMessagesQuery, GetAdminChatThreadMessagesQueryVariables>;
export const GetAdminWorkspaceChatThreadsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminWorkspaceChatThreads"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminWorkspaceChatThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<GetAdminWorkspaceChatThreadsQuery, GetAdminWorkspaceChatThreadsQueryVariables>;
export const GetVersionInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetVersionInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentVersion"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}}]}}]}}]} as unknown as DocumentNode<GetVersionInfoQuery, GetVersionInfoQueryVariables>;
export const WorkspaceLookupAdminPanelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceLookupAdminPanel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceLookupAdminPanel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"allowImpersonation"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"activationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customUrl"}},{"kind":"Field","name":{"kind":"Name","value":"subdomainUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featureFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]}}]} as unknown as DocumentNode<WorkspaceLookupAdminPanelQuery, WorkspaceLookupAdminPanelQueryVariables>;
export const DeleteJobsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteJobs"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteJobs"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"queueName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}}},{"kind":"Argument","name":{"kind":"Name","value":"jobIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletedCount"}},{"kind":"Field","name":{"kind":"Name","value":"results"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"jobId"}},{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]}}]} as unknown as DocumentNode<DeleteJobsMutation, DeleteJobsMutationVariables>;
export const RetryJobsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RetryJobs"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"retryJobs"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"queueName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}}},{"kind":"Argument","name":{"kind":"Name","value":"jobIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"retriedCount"}},{"kind":"Field","name":{"kind":"Name","value":"results"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"jobId"}},{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]}}]} as unknown as DocumentNode<RetryJobsMutation, RetryJobsMutationVariables>;
export const GetIndicatorHealthStatusDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetIndicatorHealthStatus"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indicatorId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"HealthIndicatorId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getIndicatorHealthStatus"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indicatorId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indicatorId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}},{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"queues"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"queueName"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]}}]} as unknown as DocumentNode<GetIndicatorHealthStatusQuery, GetIndicatorHealthStatusQueryVariables>;
@@ -5621,11 +5621,6 @@ msgstr "Geniet 'n 30-dae gratis proeflopie"
msgid "Enter a JSON object"
msgstr "Voer 'n JSON-voorwerp in"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "استمتع بفترة تجريبية مجانية لمدة 30 يومً
msgid "Enter a JSON object"
msgstr "أدخل كائن JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Gaudiu d'una prova gratuïta de 30 dies"
msgid "Enter a JSON object"
msgstr "Introdueix un objecte JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Využijte 30denní bezplatnou zkušební verzi"
msgid "Enter a JSON object"
msgstr "Zadejte objekt JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Få en 30-dages gratis prøveperiode"
msgid "Enter a JSON object"
msgstr "Indtast et JSON-objekt"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Genießen Sie eine 30-tägige kostenlose Testphase"
msgid "Enter a JSON object"
msgstr "Geben Sie ein JSON-Objekt ein"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Απολαύστε μια δωρεάν δοκιμή 30 ημερών"
msgid "Enter a JSON object"
msgstr "Εισάγετε ένα αντικείμενο JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
-5
View File
@@ -5616,11 +5616,6 @@ msgstr "Enjoy a 30-day free trial"
msgid "Enter a JSON object"
msgstr "Enter a JSON object"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr "Enter a new secret value"
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Disfruta de una prueba gratuita de 30 días"
msgid "Enter a JSON object"
msgstr "Introduce un objeto JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Nauti 30 päivän ilmaisesta kokeilusta"
msgid "Enter a JSON object"
msgstr "Anna JSON-objekti"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Profitez d'un essai gratuit de 30 jours"
msgid "Enter a JSON object"
msgstr "Saisissez un objet JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5621,11 +5621,6 @@ msgstr "התחל תקופת ניסיון חינם ל-30 יום"
msgid "Enter a JSON object"
msgstr "הזן אובייקט JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Élvezze a 30 napos ingyenes próbaidőszakot"
msgid "Enter a JSON object"
msgstr "Adjon meg egy JSON objektumot"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Approfitta di una prova gratuita di 30 giorni"
msgid "Enter a JSON object"
msgstr "Inserisci un oggetto JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "30 日間の無料トライアルをご利用ください"
msgid "Enter a JSON object"
msgstr "JSON オブジェクトを入力"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "30일 무료 체험을 이용해 보세요"
msgid "Enter a JSON object"
msgstr "JSON 객체를 입력하세요"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Profiteer van een gratis proefperiode van 30 dagen"
msgid "Enter a JSON object"
msgstr "Voer een JSON-object in"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Få en 30-dagers gratis prøveperiode"
msgid "Enter a JSON object"
msgstr "Skriv inn et JSON-objekt"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Skorzystaj z 30-dniowego bezpłatnego okresu próbnego"
msgid "Enter a JSON object"
msgstr "Wprowadź obiekt JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5616,11 +5616,6 @@ msgstr ""
msgid "Enter a JSON object"
msgstr ""
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Aproveite um teste gratuito de 30 dias"
msgid "Enter a JSON object"
msgstr "Insira um objeto JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Aproveite uma avaliação gratuita de 30 dias"
msgid "Enter a JSON object"
msgstr "Introduza um objeto JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Bucurați-vă de o perioadă de probă gratuită de 30 de zile"
msgid "Enter a JSON object"
msgstr "Introduceți un obiect JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
Binary file not shown.
@@ -5621,11 +5621,6 @@ msgstr "Уживајте у 30-дневном бесплатном пробно
msgid "Enter a JSON object"
msgstr "Унесите JSON објекат"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Prova gratis i 30 dagar"
msgid "Enter a JSON object"
msgstr "Ange ett JSON-objekt"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "30 günlük ücretsiz denemeden yararlanın"
msgid "Enter a JSON object"
msgstr "Bir JSON nesnesi girin"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Скористайтеся 30-денним безкоштовним пр
msgid "Enter a JSON object"
msgstr "Введіть об’єкт JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "Tận hưởng bản dùng thử miễn phí 30 ngày"
msgid "Enter a JSON object"
msgstr "Nhập một đối tượng JSON"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "享受 30 天免费试用"
msgid "Enter a JSON object"
msgstr "输入 JSON 对象"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -5621,11 +5621,6 @@ msgstr "享有 30 天免費試用"
msgid "Enter a JSON object"
msgstr "輸入 JSON 物件"
#. js-lingui-id: peBb6B
#: src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx
msgid "Enter a new secret value"
msgstr ""
#. js-lingui-id: u2fAme
#: src/modules/object-record/record-field/ui/form-types/components/FormNumberFieldInput.tsx
msgid "Enter a number"
@@ -4,13 +4,12 @@ import { isDefined } from 'twenty-shared/utils';
import { AIChatSuggestedPrompts } from '@/ai/components/suggested-prompts/AIChatSuggestedPrompts';
import { AGENT_CHAT_NEW_THREAD_DRAFT_KEY } from '@/ai/states/agentChatDraftsByThreadIdState';
import { agentChatErrorComponentFamilyState } from '@/ai/states/agentChatErrorComponentFamilyState';
import { agentChatHasMessageComponentSelector } from '@/ai/states/agentChatHasMessageComponentSelector';
import { agentChatMessagesLoadingState } from '@/ai/states/agentChatMessagesLoadingState';
import { agentChatThreadsLoadingState } from '@/ai/states/agentChatThreadsLoadingState';
import { agentChatErrorState } from '@/ai/states/agentChatErrorState';
import { agentChatHasMessageComponentSelector } from '@/ai/states/agentChatHasMessageComponentSelector';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { skipMessagesSkeletonUntilLoadedState } from '@/ai/states/skipMessagesSkeletonUntilLoadedState';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
@@ -27,11 +26,7 @@ type AIChatEmptyStateProps = {
};
export const AIChatEmptyState = ({ editor }: AIChatEmptyStateProps) => {
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const agentChatError = useAtomComponentFamilyStateValue(
agentChatErrorComponentFamilyState,
{ threadId: currentAIChatThread },
);
const agentChatError = useAtomStateValue(agentChatErrorState);
const agentChatThreadsLoading = useAtomStateValue(
agentChatThreadsLoadingState,
);
@@ -41,6 +36,7 @@ export const AIChatEmptyState = ({ editor }: AIChatEmptyStateProps) => {
const skipMessagesSkeletonUntilLoaded = useAtomStateValue(
skipMessagesSkeletonUntilLoadedState,
);
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const hasMessages = useAtomComponentSelectorValue(
agentChatHasMessageComponentSelector,
@@ -1,12 +1,10 @@
import { AIChatErrorRenderer } from '@/ai/components/AIChatErrorRenderer';
import { AgentMessageRole } from '@/ai/constants/AgentMessageRole';
import { agentChatDisplayedThreadState } from '@/ai/states/agentChatDisplayedThreadState';
import { agentChatErrorComponentFamilyState } from '@/ai/states/agentChatErrorComponentFamilyState';
import { agentChatIsStreamingComponentFamilyState } from '@/ai/states/agentChatIsStreamingComponentFamilyState';
import { agentChatErrorState } from '@/ai/states/agentChatErrorState';
import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState';
import { agentChatMessageComponentFamilySelector } from '@/ai/states/agentChatMessageComponentFamilySelector';
import { agentChatMessageIdsComponentSelector } from '@/ai/states/agentChatMessageIdsComponentSelector';
import { useAtomComponentFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorValue';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
@@ -17,17 +15,8 @@ const StyledErrorWrapper = styled.div`
`;
export const AIChatErrorUnderMessageList = () => {
const agentChatDisplayedThread = useAtomStateValue(
agentChatDisplayedThreadState,
);
const agentChatError = useAtomComponentFamilyStateValue(
agentChatErrorComponentFamilyState,
{ threadId: agentChatDisplayedThread },
);
const agentChatIsStreaming = useAtomComponentFamilyStateValue(
agentChatIsStreamingComponentFamilyState,
{ threadId: agentChatDisplayedThread },
);
const agentChatError = useAtomStateValue(agentChatErrorState);
const agentChatIsStreaming = useAtomStateValue(agentChatIsStreamingState);
const agentChatMessageIds = useAtomComponentSelectorValue(
agentChatMessageIdsComponentSelector,
@@ -1,9 +1,7 @@
import { AIChatMessage } from '@/ai/components/AIChatMessage';
import { agentChatDisplayedThreadState } from '@/ai/states/agentChatDisplayedThreadState';
import { agentChatErrorComponentFamilyState } from '@/ai/states/agentChatErrorComponentFamilyState';
import { agentChatIsStreamingComponentFamilyState } from '@/ai/states/agentChatIsStreamingComponentFamilyState';
import { agentChatErrorState } from '@/ai/states/agentChatErrorState';
import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState';
import { agentChatLastMessageIdComponentSelector } from '@/ai/states/agentChatLastMessageIdComponentSelector';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
@@ -13,17 +11,8 @@ export const AIChatLastMessageWithStreamingState = () => {
agentChatLastMessageIdComponentSelector,
);
const agentChatDisplayedThread = useAtomStateValue(
agentChatDisplayedThreadState,
);
const agentChatIsStreaming = useAtomComponentFamilyStateValue(
agentChatIsStreamingComponentFamilyState,
{ threadId: agentChatDisplayedThread },
);
const agentChatError = useAtomComponentFamilyStateValue(
agentChatErrorComponentFamilyState,
{ threadId: agentChatDisplayedThread },
);
const agentChatIsStreaming = useAtomStateValue(agentChatIsStreamingState);
const agentChatError = useAtomStateValue(agentChatErrorState);
if (!isDefined(lastMessageId)) {
return null;
@@ -1,11 +1,9 @@
import { styled } from '@linaria/react';
import { AIChatErrorRenderer } from '@/ai/components/AIChatErrorRenderer';
import { agentChatErrorComponentFamilyState } from '@/ai/states/agentChatErrorComponentFamilyState';
import { agentChatErrorState } from '@/ai/states/agentChatErrorState';
import { agentChatHasMessageComponentSelector } from '@/ai/states/agentChatHasMessageComponentSelector';
import { agentChatIsLoadingState } from '@/ai/states/agentChatIsLoadingState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
@@ -20,11 +18,7 @@ const StyledErrorContainer = styled.div`
export const AIChatStandaloneError = () => {
const agentChatIsLoading = useAtomStateValue(agentChatIsLoadingState);
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const agentChatError = useAtomComponentFamilyStateValue(
agentChatErrorComponentFamilyState,
{ threadId: currentAIChatThread },
);
const agentChatError = useAtomStateValue(agentChatErrorState);
const hasMessages = useAtomComponentSelectorValue(
agentChatHasMessageComponentSelector,
@@ -4,8 +4,8 @@ import { type AgentChatSubscriptionEvent } from 'twenty-shared/ai';
import { isDefined } from 'twenty-shared/utils';
import { AGENT_CHAT_REFETCH_MESSAGES_EVENT_NAME } from '@/ai/constants/AgentChatRefetchMessagesEventName';
import { agentChatFirstLiveSeqComponentFamilyState } from '@/ai/states/agentChatFirstLiveSeqComponentFamilyState';
import { agentChatHandleEventCallbackComponentFamilyState } from '@/ai/states/agentChatHandleEventCallbackComponentFamilyState';
import { agentChatFirstLiveSeqState } from '@/ai/states/agentChatFirstLiveSeqState';
import { agentChatHandleEventCallbackState } from '@/ai/states/agentChatHandleEventCallbackState';
import { AGENT_CHAT_NEW_THREAD_DRAFT_KEY } from '@/ai/states/agentChatDraftsByThreadIdState';
import { agentChatFetchedMessagesComponentFamilyState } from '@/ai/states/agentChatFetchedMessagesComponentFamilyState';
import { agentChatMessagesLoadingState } from '@/ai/states/agentChatMessagesLoadingState';
@@ -15,7 +15,6 @@ import { skipMessagesSkeletonUntilLoadedState } from '@/ai/states/skipMessagesSk
import { mapDBMessagesToUIMessages } from '@/ai/utils/mapDBMessagesToUIMessages';
import { useQueryWithCallbacks } from '@/apollo/hooks/useQueryWithCallbacks';
import { useListenToBrowserEvent } from '@/browser-event/hooks/useListenToBrowserEvent';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomComponentFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentFamilyState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
@@ -53,14 +52,6 @@ export const AgentChatMessagesFetchEffect = () => {
{ threadId: currentAIChatThread },
);
const handleEventCallbackFamilyCallback =
useAtomComponentFamilyStateCallbackState(
agentChatHandleEventCallbackComponentFamilyState,
);
const firstLiveSeqFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatFirstLiveSeqComponentFamilyState,
);
const handleFirstLoad = useCallback(
(_data: GetChatMessagesQuery) => {
setSkipMessagesSkeletonUntilLoaded(false);
@@ -84,23 +75,13 @@ export const AgentChatMessagesFetchEffect = () => {
return;
}
const threadId = store.get(currentAIChatThreadState.atom);
if (!isDefined(threadId)) {
return;
}
const familyKey = { threadId };
const handleEvent = store.get(
handleEventCallbackFamilyCallback(familyKey),
);
const handleEvent = store.get(agentChatHandleEventCallbackState.atom);
if (!isDefined(handleEvent)) {
return;
}
const firstLiveSeq = store.get(firstLiveSeqFamilyCallback(familyKey));
const firstLiveSeq = store.get(agentChatFirstLiveSeqState.atom);
for (let index = 0; index < catchup.chunks.length; index++) {
const chunkSeq = index + 1;
@@ -116,13 +97,7 @@ export const AgentChatMessagesFetchEffect = () => {
} as AgentChatSubscriptionEvent);
}
},
[
setAgentChatFetchedMessages,
setAgentChatQueuedMessages,
store,
handleEventCallbackFamilyCallback,
firstLiveSeqFamilyCallback,
],
[setAgentChatFetchedMessages, setAgentChatQueuedMessages, store],
);
const handleLoadingChange = useCallback(
@@ -11,7 +11,7 @@ import { agentChatDisplayedThreadState } from '@/ai/states/agentChatDisplayedThr
import { agentChatFetchedMessagesComponentFamilyState } from '@/ai/states/agentChatFetchedMessagesComponentFamilyState';
import { agentChatIsInitialScrollPendingOnThreadChangeState } from '@/ai/states/agentChatIsInitialScrollPendingOnThreadChangeState';
import { agentChatIsLoadingState } from '@/ai/states/agentChatIsLoadingState';
import { agentChatIsStreamingComponentFamilyState } from '@/ai/states/agentChatIsStreamingComponentFamilyState';
import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState';
import { agentChatMessagesComponentFamilyState } from '@/ai/states/agentChatMessagesComponentFamilyState';
import { agentChatMessagesLoadingState } from '@/ai/states/agentChatMessagesLoadingState';
import { agentChatThreadsLoadingState } from '@/ai/states/agentChatThreadsLoadingState';
@@ -57,10 +57,7 @@ export const AgentChatStreamSubscriptionEffect = () => {
{ threadId: currentAIChatThread },
);
const agentChatIsStreaming = useAtomComponentFamilyStateValue(
agentChatIsStreamingComponentFamilyState,
{ threadId: currentAIChatThread },
);
const agentChatIsStreaming = useAtomStateValue(agentChatIsStreamingState);
const agentChatDisplayedThread = useAtomStateValue(
agentChatDisplayedThreadState,
@@ -9,16 +9,15 @@ import {
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { agentChatThreadsLoadingState } from '@/ai/states/agentChatThreadsLoadingState';
import { agentChatThreadsSelector } from '@/ai/states/agentChatThreadsSelector';
import { agentChatUsageComponentFamilyState } from '@/ai/states/agentChatUsageComponentFamilyState';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleComponentFamilyState } from '@/ai/states/currentAIChatThreadTitleComponentFamilyState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { hasInitializedAgentChatThreadsState } from '@/ai/states/hasInitializedAgentChatThreadsState';
import { hasTriggeredCreateForDraftState } from '@/ai/states/hasTriggeredCreateForDraftState';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { type FlatAgentChatThread } from '@/metadata-store/types/FlatAgentChatThread';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
@@ -38,15 +37,13 @@ export const AgentChatThreadInitializationEffect = () => {
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const setCurrentAIChatThread = useSetAtomState(currentAIChatThreadState);
const setAgentChatInput = useSetAtomState(agentChatInputState);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);
const setAgentChatThreadsLoading = useSetAtomState(
agentChatThreadsLoadingState,
);
const threadTitleFamilyCallback = useAtomComponentFamilyStateCallbackState(
currentAIChatThreadTitleComponentFamilyState,
);
const agentChatUsageFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatUsageComponentFamilyState,
);
const store = useStore();
const agentChatThreads = useAtomStateValue(agentChatThreadsSelector);
const storeEntry = useAtomValue(
@@ -116,20 +113,13 @@ export const AgentChatThreadInitializationEffect = () => {
setCurrentAIChatThread(firstThread.id);
setAgentChatInput(draftForThread);
const firstThreadFamilyKey = { threadId: firstThread.id };
store.set(
threadTitleFamilyCallback(firstThreadFamilyKey),
firstThread.title ?? null,
);
setCurrentAIChatThreadTitle(firstThread.title ?? null);
const hasUsageData =
(firstThread.conversationSize ?? 0) > 0 &&
isDefined(firstThread.contextWindowTokens);
store.set(
agentChatUsageFamilyCallback(firstThreadFamilyKey),
setAgentChatUsage(
hasUsageData
? {
lastMessage: null,
@@ -150,6 +140,8 @@ export const AgentChatThreadInitializationEffect = () => {
AGENT_CHAT_NEW_THREAD_DRAFT_KEY
] ?? '',
);
setCurrentAIChatThreadTitle(null);
setAgentChatUsage(null);
}
}, [
agentChatThreads,
@@ -160,9 +152,9 @@ export const AgentChatThreadInitializationEffect = () => {
storeEntry.status,
setCurrentAIChatThread,
setAgentChatInput,
setCurrentAIChatThreadTitle,
setAgentChatUsage,
store,
threadTitleFamilyCallback,
agentChatUsageFamilyCallback,
]);
return null;
@@ -10,13 +10,11 @@ import { themeCssVariables } from 'twenty-ui/theme-constants';
import { ContextUsageProgressRing } from '@/ai/components/internal/ContextUsageProgressRing';
import { agentChatHasMessageComponentSelector } from '@/ai/states/agentChatHasMessageComponentSelector';
import {
agentChatUsageComponentFamilyState,
agentChatUsageState,
type AgentChatLastMessageUsage,
} from '@/ai/states/agentChatUsageComponentFamilyState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
} from '@/ai/states/agentChatUsageState';
import { SettingsBillingLabelValueItem } from '@/settings/billing/components/internal/SettingsBillingLabelValueItem';
import { billingState } from '@/client-config/states/billingState';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { formatNumber } from '~/utils/format/formatNumber';
@@ -96,11 +94,7 @@ const getCachedLabel = (lastMessage: AgentChatLastMessageUsage): string => {
export const AIChatContextUsageButton = () => {
const { t } = useLingui();
const [isHovered, setIsHovered] = useState(false);
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const agentChatUsage = useAtomComponentFamilyStateValue(
agentChatUsageComponentFamilyState,
{ threadId: currentAIChatThread },
);
const agentChatUsage = useAtomStateValue(agentChatUsageState);
const billing = useAtomStateValue(billingState);
const isBillingEnabled = billing?.isBillingEnabled ?? false;
@@ -1,10 +1,8 @@
import { AGENT_CHAT_STOP_EVENT_NAME } from '@/ai/constants/AgentChatStopEventName';
import { agentChatInputIsEmptySelector } from '@/ai/states/agentChatInputIsEmptySelector';
import { agentChatIsLoadingState } from '@/ai/states/agentChatIsLoadingState';
import { agentChatIsStreamingComponentFamilyState } from '@/ai/states/agentChatIsStreamingComponentFamilyState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState';
import { dispatchBrowserEvent } from '@/browser-event/utils/dispatchBrowserEvent';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { IconArrowUp, IconPlayerStop } from 'twenty-ui/display';
import { RoundedIconButton } from 'twenty-ui/input';
@@ -20,11 +18,7 @@ export const SendMessageButton = ({ onSend }: SendMessageButtonProps) => {
const agentChatIsLoading = useAtomStateValue(agentChatIsLoadingState);
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const agentChatIsStreaming = useAtomComponentFamilyStateValue(
agentChatIsStreamingComponentFamilyState,
{ threadId: currentAIChatThread },
);
const agentChatIsStreaming = useAtomStateValue(agentChatIsStreamingState);
const handleStopClick = () => {
dispatchBrowserEvent(AGENT_CHAT_STOP_EVENT_NAME);
@@ -1,11 +1,10 @@
import { agentChatDraftsByThreadIdState } from '@/ai/states/agentChatDraftsByThreadIdState';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { agentChatUsageComponentFamilyState } from '@/ai/states/agentChatUsageComponentFamilyState';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleComponentFamilyState } from '@/ai/states/currentAIChatThreadTitleComponentFamilyState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { threadIdCreatedFromDraftState } from '@/ai/states/threadIdCreatedFromDraftState';
import { useOpenAskAIPageInSidePanel } from '@/side-panel/hooks/useOpenAskAIPageInSidePanel';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useStore } from 'jotai';
@@ -27,15 +26,13 @@ export const useAIChatThreadClick = (
currentAIChatThreadState,
);
const setAgentChatInput = useSetAtomState(agentChatInputState);
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const setAgentChatDraftsByThreadId = useSetAtomState(
agentChatDraftsByThreadIdState,
);
const threadTitleFamilyCallback = useAtomComponentFamilyStateCallbackState(
currentAIChatThreadTitleComponentFamilyState,
);
const agentChatUsageFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatUsageComponentFamilyState,
);
const store = useStore();
const { openAskAIPage } = useOpenAskAIPageInSidePanel();
@@ -57,18 +54,12 @@ export const useAIChatThreadClick = (
setAgentChatInput(newDraft);
}
const clickedFamilyKey = { threadId: thread.id };
store.set(
threadTitleFamilyCallback(clickedFamilyKey),
thread.title ?? null,
);
setCurrentAIChatThreadTitle(thread.title ?? null);
const hasUsageData =
(thread.conversationSize ?? 0) > 0 &&
isDefined(thread.contextWindowTokens);
store.set(
agentChatUsageFamilyCallback(clickedFamilyKey),
setAgentChatUsage(
hasUsageData
? {
lastMessage: null,
@@ -10,17 +10,17 @@ import {
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { AGENT_CHAT_INSTANCE_ID } from '@/ai/constants/AgentChatInstanceId';
import { AGENT_CHAT_REFETCH_MESSAGES_EVENT_NAME } from '@/ai/constants/AgentChatRefetchMessagesEventName';
import { ON_AGENT_CHAT_EVENT } from '@/ai/graphql/subscriptions/OnAgentChatEvent';
import { agentChatErrorComponentFamilyState } from '@/ai/states/agentChatErrorComponentFamilyState';
import { agentChatFirstLiveSeqComponentFamilyState } from '@/ai/states/agentChatFirstLiveSeqComponentFamilyState';
import { agentChatHandleEventCallbackComponentFamilyState } from '@/ai/states/agentChatHandleEventCallbackComponentFamilyState';
import { agentChatIsStreamingComponentFamilyState } from '@/ai/states/agentChatIsStreamingComponentFamilyState';
import { agentChatErrorState } from '@/ai/states/agentChatErrorState';
import { agentChatFirstLiveSeqState } from '@/ai/states/agentChatFirstLiveSeqState';
import { agentChatHandleEventCallbackState } from '@/ai/states/agentChatHandleEventCallbackState';
import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState';
import { agentChatMessagesComponentFamilyState } from '@/ai/states/agentChatMessagesComponentFamilyState';
import { agentChatUsageComponentFamilyState } from '@/ai/states/agentChatUsageComponentFamilyState';
import { currentAIChatThreadTitleComponentFamilyState } from '@/ai/states/currentAIChatThreadTitleComponentFamilyState';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { dispatchBrowserEvent } from '@/browser-event/utils/dispatchBrowserEvent';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { sseClientState } from '@/sse-db-event/states/sseClientState';
@@ -96,52 +96,18 @@ export const useAgentChatSubscription = (threadId: string | null) => {
const store = useStore();
const sseClient = useAtomStateValue(sseClientState);
const errorFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatErrorComponentFamilyState,
);
const isStreamingFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatIsStreamingComponentFamilyState,
);
const firstLiveSeqFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatFirstLiveSeqComponentFamilyState,
);
const handleEventCallbackFamilyCallback =
useAtomComponentFamilyStateCallbackState(
agentChatHandleEventCallbackComponentFamilyState,
);
const messagesFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatMessagesComponentFamilyState,
);
const usageFamilyCallback = useAtomComponentFamilyStateCallbackState(
agentChatUsageComponentFamilyState,
);
const threadTitleFamilyCallback = useAtomComponentFamilyStateCallbackState(
currentAIChatThreadTitleComponentFamilyState,
);
useEffect(() => {
if (!isDefined(threadId) || !isDefined(sseClient)) {
return;
}
const familyKey = { threadId };
const errorAtom = errorFamilyCallback(familyKey);
const isStreamingAtom = isStreamingFamilyCallback(familyKey);
const firstLiveSeqAtom = firstLiveSeqFamilyCallback(familyKey);
const handleEventCallbackAtom =
handleEventCallbackFamilyCallback(familyKey);
const messagesAtom = messagesFamilyCallback(familyKey);
const usageAtom = usageFamilyCallback(familyKey);
const threadTitleAtom = threadTitleFamilyCallback(familyKey);
let bridge: TransformStream<UIMessageChunk> | null = null;
let throttleTimer: ReturnType<typeof setTimeout> | null = null;
let latestMessage: ExtendedUIMessage | null = null;
let writer: WritableStreamDefaultWriter<UIMessageChunk> | null = null;
let disposed = false;
store.set(firstLiveSeqAtom, null);
store.set(agentChatFirstLiveSeqState.atom, null);
const closeWriter = () => {
if (isDefined(writer)) {
@@ -153,8 +119,8 @@ export const useAgentChatSubscription = (threadId: string | null) => {
const cleanupStream = () => {
closeWriter();
if (store.get(isStreamingAtom)) {
store.set(isStreamingAtom, false);
if (store.get(agentChatIsStreamingState.atom)) {
store.set(agentChatIsStreamingState.atom, false);
}
};
@@ -165,7 +131,14 @@ export const useAgentChatSubscription = (threadId: string | null) => {
return;
}
const currentMessages = store.get(messagesAtom);
const atomKey = {
instanceId: AGENT_CHAT_INSTANCE_ID,
familyKey: { threadId },
};
const currentMessages = store.get(
agentChatMessagesComponentFamilyState.atomFamily(atomKey),
);
const streamingMsgIndex = currentMessages.findIndex(
(message) => message.id === messageToFlush.id,
@@ -175,9 +148,15 @@ export const useAgentChatSubscription = (threadId: string | null) => {
const updatedMessages = [...currentMessages];
updatedMessages[streamingMsgIndex] = messageToFlush;
store.set(messagesAtom, updatedMessages);
store.set(
agentChatMessagesComponentFamilyState.atomFamily(atomKey),
updatedMessages,
);
} else {
store.set(messagesAtom, [...currentMessages, messageToFlush]);
store.set(agentChatMessagesComponentFamilyState.atomFamily(atomKey), [
...currentMessages,
messageToFlush,
]);
}
};
@@ -205,7 +184,7 @@ export const useAgentChatSubscription = (threadId: string | null) => {
);
if (isDefined(titlePart) && titlePart.type === 'data-thread-title') {
store.set(threadTitleAtom, titlePart.data.title);
store.set(currentAIChatThreadTitleState.atom, titlePart.data.title);
}
const metadata = extendedMessage.metadata as
@@ -228,7 +207,7 @@ export const useAgentChatSubscription = (threadId: string | null) => {
const usage = metadata.usage;
const model = metadata.model;
store.set(usageAtom, (prev) => ({
store.set(agentChatUsageState.atom, (prev) => ({
lastMessage: {
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
@@ -255,20 +234,22 @@ export const useAgentChatSubscription = (threadId: string | null) => {
flushToAtom();
if (!disposed) {
store.set(isStreamingAtom, false);
store.set(agentChatIsStreamingState.atom, false);
}
};
const handleEvent = (event: AgentChatSubscriptionEvent) => {
switch (event.type) {
case 'stream-chunk': {
if (isDefined(event.seq) && store.get(firstLiveSeqAtom) === null) {
store.set(firstLiveSeqAtom, event.seq);
if (
isDefined(event.seq) &&
store.get(agentChatFirstLiveSeqState.atom) === null
) {
store.set(agentChatFirstLiveSeqState.atom, event.seq);
}
if (!store.get(isStreamingAtom)) {
store.set(isStreamingAtom, true);
store.set(errorAtom, null);
if (!store.get(agentChatIsStreamingState.atom)) {
store.set(agentChatIsStreamingState.atom, true);
bridge = new TransformStream<UIMessageChunk>();
writer = bridge.writable.getWriter();
@@ -279,7 +260,7 @@ export const useAgentChatSubscription = (threadId: string | null) => {
startReadLoop(adaptedReadable).catch(() => {
if (!disposed) {
store.set(isStreamingAtom, false);
store.set(agentChatIsStreamingState.atom, false);
}
});
}
@@ -307,16 +288,16 @@ export const useAgentChatSubscription = (threadId: string | null) => {
};
streamError.code = event.code;
store.set(errorAtom, streamError);
store.set(agentChatErrorState.atom, streamError);
closeWriter();
store.set(isStreamingAtom, false);
store.set(agentChatIsStreamingState.atom, false);
break;
}
}
};
store.set(handleEventCallbackAtom, () => handleEvent);
store.set(agentChatHandleEventCallbackState.atom, () => handleEvent);
const dispose = sseClient.subscribe<AgentChatEventPayload>(
{
@@ -344,23 +325,12 @@ export const useAgentChatSubscription = (threadId: string | null) => {
return () => {
disposed = true;
store.set(handleEventCallbackAtom, null);
store.set(agentChatHandleEventCallbackState.atom, null);
if (isDefined(throttleTimer)) {
clearTimeout(throttleTimer);
}
cleanupStream();
dispose();
};
}, [
threadId,
sseClient,
store,
errorFamilyCallback,
isStreamingFamilyCallback,
firstLiveSeqFamilyCallback,
handleEventCallbackFamilyCallback,
messagesFamilyCallback,
usageFamilyCallback,
threadTitleFamilyCallback,
]);
}, [threadId, sseClient, store]);
};
@@ -5,7 +5,9 @@ import {
agentChatDraftsByThreadIdState,
} from '@/ai/states/agentChatDraftsByThreadIdState';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { shouldFocusChatEditorState } from '@/ai/states/shouldFocusChatEditorState';
import { hasTriggeredCreateForDraftState } from '@/ai/states/hasTriggeredCreateForDraftState';
import { isCreatingChatThreadState } from '@/ai/states/isCreatingChatThreadState';
@@ -22,6 +24,10 @@ import { CreateChatThreadDocument } from '~/generated-metadata/graphql';
export const useCreateAgentChatThread = () => {
const setCurrentAIChatThread = useSetAtomState(currentAIChatThreadState);
const setAgentChatInput = useSetAtomState(agentChatInputState);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);
const setIsCreatingChatThread = useSetAtomState(isCreatingChatThreadState);
const setAgentChatDraftsByThreadId = useSetAtomState(
agentChatDraftsByThreadIdState,
@@ -81,6 +87,8 @@ export const useCreateAgentChatThread = () => {
setCurrentAIChatThread(newThreadId);
setAgentChatInput(newDraft);
setCurrentAIChatThreadTitle(null);
setAgentChatUsage(null);
},
onError: () => {
setIsCreatingChatThread(false);
@@ -5,7 +5,9 @@ import {
agentChatDraftsByThreadIdState,
} from '@/ai/states/agentChatDraftsByThreadIdState';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { shouldFocusChatEditorState } from '@/ai/states/shouldFocusChatEditorState';
import { hasTriggeredCreateForDraftState } from '@/ai/states/hasTriggeredCreateForDraftState';
import { threadIdCreatedFromDraftState } from '@/ai/states/threadIdCreatedFromDraftState';
@@ -21,6 +23,10 @@ export const useSwitchToNewAIChat = () => {
currentAIChatThreadState,
);
const setAgentChatInput = useSetAtomState(agentChatInputState);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);
const setAgentChatDraftsByThreadId = useSetAtomState(
agentChatDraftsByThreadIdState,
);
@@ -42,6 +48,8 @@ export const useSwitchToNewAIChat = () => {
store.set(hasTriggeredCreateForDraftState.atom, false);
setCurrentAIChatThread(AGENT_CHAT_NEW_THREAD_DRAFT_KEY);
setAgentChatInput(newChatDraft);
setCurrentAIChatThreadTitle(null);
setAgentChatUsage(null);
openAskAIPage();
store.set(shouldFocusChatEditorState.atom, true);
};
@@ -1,9 +0,0 @@
import { AgentChatComponentInstanceContext } from '@/ai/states/AgentChatComponentInstanceContext';
import { createAtomComponentFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomComponentFamilyState';
export const agentChatErrorComponentFamilyState =
createAtomComponentFamilyState<Error | null, { threadId: string | null }>({
key: 'agentChatErrorComponentFamilyState',
defaultValue: null,
componentInstanceContext: AgentChatComponentInstanceContext,
});
@@ -0,0 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatErrorState = createAtomState<Error | undefined | null>({
key: 'agentChatErrorState',
defaultValue: null,
});
@@ -1,9 +0,0 @@
import { AgentChatComponentInstanceContext } from '@/ai/states/AgentChatComponentInstanceContext';
import { createAtomComponentFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomComponentFamilyState';
export const agentChatFirstLiveSeqComponentFamilyState =
createAtomComponentFamilyState<number | null, { threadId: string | null }>({
key: 'agentChatFirstLiveSeqComponentFamilyState',
defaultValue: null,
componentInstanceContext: AgentChatComponentInstanceContext,
});
@@ -0,0 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatFirstLiveSeqState = createAtomState<number | null>({
key: 'agentChatFirstLiveSeqState',
defaultValue: null,
});
@@ -1,14 +0,0 @@
import { type AgentChatSubscriptionEvent } from 'twenty-shared/ai';
import { AgentChatComponentInstanceContext } from '@/ai/states/AgentChatComponentInstanceContext';
import { createAtomComponentFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomComponentFamilyState';
export const agentChatHandleEventCallbackComponentFamilyState =
createAtomComponentFamilyState<
((event: AgentChatSubscriptionEvent) => void) | null,
{ threadId: string | null }
>({
key: 'agentChatHandleEventCallbackComponentFamilyState',
defaultValue: null,
componentInstanceContext: AgentChatComponentInstanceContext,
});
@@ -0,0 +1,10 @@
import { type AgentChatSubscriptionEvent } from 'twenty-shared/ai';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatHandleEventCallbackState = createAtomState<
((event: AgentChatSubscriptionEvent) => void) | null
>({
key: 'agentChatHandleEventCallbackState',
defaultValue: null,
});
@@ -1,9 +0,0 @@
import { AgentChatComponentInstanceContext } from '@/ai/states/AgentChatComponentInstanceContext';
import { createAtomComponentFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomComponentFamilyState';
export const agentChatIsStreamingComponentFamilyState =
createAtomComponentFamilyState<boolean, { threadId: string | null }>({
key: 'agentChatIsStreamingComponentFamilyState',
defaultValue: false,
componentInstanceContext: AgentChatComponentInstanceContext,
});
@@ -0,0 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatIsStreamingState = createAtomState({
key: 'agentChatIsStreamingState',
defaultValue: false,
});
@@ -1,30 +0,0 @@
import { AgentChatComponentInstanceContext } from '@/ai/states/AgentChatComponentInstanceContext';
import { createAtomComponentFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomComponentFamilyState';
export type AgentChatLastMessageUsage = {
inputTokens: number;
outputTokens: number;
cachedInputTokens: number;
inputCredits: number;
outputCredits: number;
};
export type AgentChatUsageState = {
lastMessage: AgentChatLastMessageUsage | null;
conversationSize: number;
contextWindowTokens: number;
inputTokens: number;
outputTokens: number;
inputCredits: number;
outputCredits: number;
};
export const agentChatUsageComponentFamilyState =
createAtomComponentFamilyState<
AgentChatUsageState | null,
{ threadId: string | null }
>({
key: 'agentChatUsageComponentFamilyState',
defaultValue: null,
componentInstanceContext: AgentChatComponentInstanceContext,
});
@@ -0,0 +1,24 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export type AgentChatLastMessageUsage = {
inputTokens: number;
outputTokens: number;
cachedInputTokens: number;
inputCredits: number;
outputCredits: number;
};
export type AgentChatUsageState = {
lastMessage: AgentChatLastMessageUsage | null;
conversationSize: number;
contextWindowTokens: number;
inputTokens: number;
outputTokens: number;
inputCredits: number;
outputCredits: number;
};
export const agentChatUsageState = createAtomState<AgentChatUsageState | null>({
key: 'agentChatUsageState',
defaultValue: null,
});
@@ -1,9 +0,0 @@
import { AgentChatComponentInstanceContext } from '@/ai/states/AgentChatComponentInstanceContext';
import { createAtomComponentFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomComponentFamilyState';
export const currentAIChatThreadTitleComponentFamilyState =
createAtomComponentFamilyState<string | null, { threadId: string | null }>({
key: 'currentAIChatThreadTitleComponentFamilyState',
defaultValue: null,
componentInstanceContext: AgentChatComponentInstanceContext,
});
@@ -0,0 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const currentAIChatThreadTitleState = createAtomState<string | null>({
key: 'ai/currentAIChatThreadTitleState',
defaultValue: null,
});
@@ -425,6 +425,30 @@ const SettingsAdminNewAiModel = lazy(() =>
),
);
const SettingsAdminUserDetail = lazy(() =>
import('~/pages/settings/admin-panel/SettingsAdminUserDetail').then(
(module) => ({
default: module.SettingsAdminUserDetail,
}),
),
);
const SettingsAdminWorkspaceDetail = lazy(() =>
import('~/pages/settings/admin-panel/SettingsAdminWorkspaceDetail').then(
(module) => ({
default: module.SettingsAdminWorkspaceDetail,
}),
),
);
const SettingsAdminWorkspaceChatThread = lazy(() =>
import('~/pages/settings/admin-panel/SettingsAdminWorkspaceChatThread').then(
(module) => ({
default: module.SettingsAdminWorkspaceChatThread,
}),
),
);
const SettingsUpdates = lazy(() =>
import('~/pages/settings/updates/SettingsUpdates').then((module) => ({
default: module.SettingsUpdates,
@@ -770,6 +794,18 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
path={SettingsPath.AdminPanelAiProviderDetail}
element={<SettingsAdminAiProviderDetail />}
/>
<Route
path={SettingsPath.AdminPanelUserDetail}
element={<SettingsAdminUserDetail />}
/>
<Route
path={SettingsPath.AdminPanelWorkspaceDetail}
element={<SettingsAdminWorkspaceDetail />}
/>
<Route
path={SettingsPath.AdminPanelWorkspaceChatThread}
element={<SettingsAdminWorkspaceChatThread />}
/>
</>
)}
@@ -0,0 +1,95 @@
import { useApolloClient } from '@apollo/client/react';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useStore } from 'jotai';
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
import { useAuth } from '@/auth/hooks/useAuth';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { useClearSseClient } from '@/sse-db-event/hooks/useClearSseClient';
import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
import { type AuthTokenPair } from '~/generated-metadata/graphql';
const IMPERSONATION_SESSION_KEY = 'impersonation_original_session';
type StoredImpersonationSession = {
tokenPair: AuthTokenPair;
returnPath: string;
};
export const useImpersonationSession = () => {
const store = useStore();
const client = useApolloClient();
const navigate = useNavigate();
const { getAuthTokensFromLoginToken, signOut } = useAuth();
const { clearSseClient } = useClearSseClient();
const { loadCurrentUser } = useLoadCurrentUser();
const startImpersonating = useCallback(
async (loginToken: string, returnPath?: string) => {
const currentTokenPair = store.get(tokenPairState.atom);
if (currentTokenPair) {
const session: StoredImpersonationSession = {
tokenPair: currentTokenPair,
returnPath: returnPath ?? window.location.pathname,
};
sessionStorage.setItem(
IMPERSONATION_SESSION_KEY,
JSON.stringify(session),
);
}
clearSseClient();
await client.clearStore();
store.set(isAppEffectRedirectEnabledState.atom, false);
await getAuthTokensFromLoginToken(loginToken);
store.set(isAppEffectRedirectEnabledState.atom, true);
},
[store, client, clearSseClient, getAuthTokensFromLoginToken],
);
const stopImpersonating = useCallback(async () => {
const raw = sessionStorage.getItem(IMPERSONATION_SESSION_KEY);
if (!raw) {
// No stored session — likely a cross-workspace tab opened via redirect.
// Try closing the tab (works when opened via window.open or target=_blank).
window.close();
// If window.close() was blocked by the browser, fall back to sign out.
await signOut();
return;
}
let session: StoredImpersonationSession;
try {
session = JSON.parse(raw);
} catch {
sessionStorage.removeItem(IMPERSONATION_SESSION_KEY);
await signOut();
return;
}
sessionStorage.removeItem(IMPERSONATION_SESSION_KEY);
clearSseClient();
await client.clearStore();
store.set(isAppEffectRedirectEnabledState.atom, false);
store.set(tokenPairState.atom, session.tokenPair);
await loadCurrentUser();
store.set(isAppEffectRedirectEnabledState.atom, true);
navigate(session.returnPath);
}, [store, client, clearSseClient, loadCurrentUser, signOut, navigate]);
const hasStoredSession = useCallback(() => {
return sessionStorage.getItem(IMPERSONATION_SESSION_KEY) !== null;
}, []);
return { startImpersonating, stopImpersonating, hasStoredSession };
};
@@ -1,4 +1,4 @@
import { useAuth } from '@/auth/hooks/useAuth';
import { useImpersonationSession } from '@/auth/hooks/useImpersonationSession';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { isImpersonatingState } from '@/auth/states/isImpersonatingState';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
@@ -11,7 +11,7 @@ export const InformationBannerIsImpersonating = () => {
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
const isImpersonating = useAtomStateValue(isImpersonatingState);
const { signOut } = useAuth();
const { stopImpersonating } = useImpersonationSession();
if (!isDefined(currentWorkspaceMember) || !isImpersonating) {
return null;
@@ -25,7 +25,7 @@ export const InformationBannerIsImpersonating = () => {
message={t`Logged in as ${impersonatedUser}`}
buttonTitle={t`Stop impersonating`}
buttonIcon={IconLogout}
buttonOnClick={signOut}
buttonOnClick={stopImpersonating}
/>
);
};
@@ -2,6 +2,8 @@ import { useMemo, useState } from 'react';
import { useMutation, useQuery } from '@apollo/client/react';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { Tag } from 'twenty-ui/components';
import { H2Title, IconBolt, IconLock, IconRobot } from 'twenty-ui/display';
import { Card, Section } from 'twenty-ui/layout';
@@ -319,6 +321,9 @@ export const SettingsAdminAI = () => {
<TableRow
key={item.key}
gridTemplateColumns={USAGE_TABLE_GRID_TEMPLATE_COLUMNS}
to={getSettingsPath(SettingsPath.AdminPanelWorkspaceDetail, {
workspaceId: item.key,
})}
>
<TableCell color={themeCssVariables.font.color.primary}>
{item.label ?? item.key}
@@ -1,134 +1,73 @@
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent';
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer';
import { ADMIN_PANEL_RECENT_USERS } from '@/settings/admin-panel/graphql/queries/adminPanelRecentUsers';
import { ADMIN_PANEL_TOP_WORKSPACES } from '@/settings/admin-panel/graphql/queries/adminPanelTopWorkspaces';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useQuery } from '@apollo/client/react';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useState } from 'react';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useMutation } from '@apollo/client/react';
import { UserLookupAdminPanelDocument } from '~/generated-metadata/graphql';
import { useDebounce } from 'use-debounce';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { currentUserState } from '@/auth/states/currentUserState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SettingsTableCard } from '@/settings/components/SettingsTableCard';
import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer';
import { SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminUserLookupWorkspaceTabsId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils';
import {
H2Title,
IconId,
IconMail,
IconSearch,
IconUser,
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
gap: ${themeCssVariables.spacing[2]};
const StyledEmptyState = styled.div`
color: ${themeCssVariables.font.color.tertiary};
padding: ${themeCssVariables.spacing[4]} 0;
`;
export const SettingsAdminGeneral = () => {
const [userIdentifier, setUserIdentifier] = useState('');
const { enqueueErrorSnackBar } = useSnackBar();
const [userSearchTerm, setUserSearchTerm] = useState('');
const [debouncedUserSearchTerm] = useDebounce(userSearchTerm, 300);
const [activeTabId, setActiveTabId] = useAtomComponentState(
activeTabIdComponentState,
SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID,
);
const [userLookupResult, setUserLookupResult] = useAtomState(
userLookupResultState,
);
const [isUserLookupLoading, setIsUserLookupLoading] = useState(false);
const [userLookup] = useMutation(UserLookupAdminPanelDocument);
const [workspaceSearchTerm, setWorkspaceSearchTerm] = useState('');
const [debouncedWorkspaceSearchTerm] = useDebounce(workspaceSearchTerm, 300);
const currentUser = useAtomStateValue(currentUserState);
const canAccessFullAdminPanel = currentUser?.canAccessFullAdminPanel;
const canImpersonate = currentUser?.canImpersonate;
const canManageFeatureFlags = useAtomStateValue(canManageFeatureFlagsState);
const handleSearch = async () => {
setActiveTabId('');
setIsUserLookupLoading(true);
setUserLookupResult(null);
const { data: recentUsersData, loading: isLoadingUsers } = useQuery<{
adminPanelRecentUsers: {
id: string;
email: string;
firstName?: string | null;
lastName?: string | null;
createdAt: string;
workspaceName?: string | null;
workspaceId?: string | null;
}[];
}>(ADMIN_PANEL_RECENT_USERS, {
variables: { searchTerm: debouncedUserSearchTerm },
skip: !canImpersonate,
});
const response = await userLookup({
variables: { userIdentifier },
onCompleted: (data) => {
setIsUserLookupLoading(false);
if (isDefined(data?.userLookupAdminPanel)) {
setUserLookupResult(data.userLookupAdminPanel);
}
},
onError: (error) => {
setIsUserLookupLoading(false);
enqueueErrorSnackBar({
apolloError: error,
});
},
});
const { data: topWorkspacesData, loading: isLoadingWorkspaces } = useQuery<{
adminPanelTopWorkspaces: {
id: string;
name: string;
totalUsers: number;
subdomain: string;
}[];
}>(ADMIN_PANEL_TOP_WORKSPACES, {
variables: { searchTerm: debouncedWorkspaceSearchTerm },
skip: !canImpersonate,
});
const result = response.data?.userLookupAdminPanel;
if (isDefined(result?.workspaces) && result.workspaces.length > 0) {
setActiveTabId(result.workspaces[0].id);
}
};
const activeWorkspace = userLookupResult?.workspaces.find(
(workspace) => workspace.id === activeTabId,
);
const tabs =
userLookupResult?.workspaces.map((workspace) => ({
id: workspace.id,
title: workspace.name,
logo:
getImageAbsoluteURI({
imageUrl: isNonEmptyString(workspace.logo)
? workspace.logo
: DEFAULT_WORKSPACE_LOGO,
baseUrl: REACT_APP_SERVER_BASE_URL,
}) ?? '',
})) ?? [];
const userFullName = `${userLookupResult?.user.firstName || ''} ${
userLookupResult?.user.lastName || ''
}`.trim();
const userInfoItems = [
{
Icon: IconUser,
label: t`Name`,
value: userFullName,
},
{
Icon: IconMail,
label: t`Email`,
value: userLookupResult?.user.email,
},
{
Icon: IconId,
label: t`ID`,
value: userLookupResult?.user.id,
},
];
const recentUsers = recentUsersData?.adminPanelRecentUsers ?? [];
const topWorkspaces = topWorkspacesData?.adminPanelTopWorkspaces ?? [];
return (
<>
@@ -143,63 +82,105 @@ export const SettingsAdminGeneral = () => {
)}
{canImpersonate && (
<Section>
<H2Title
title={
canManageFeatureFlags
? t`Feature Flags & Impersonation`
: t`User Impersonation`
}
description={
canManageFeatureFlags
? t`Look up users and manage their workspace feature flags or impersonate them.`
: t`Look up users to impersonate them.`
}
/>
<StyledContainer>
<SettingsTextInput
instanceId="admin-user-lookup"
value={userIdentifier}
onChange={setUserIdentifier}
onInputEnter={handleSearch}
placeholder={t`Enter user ID or email address`}
fullWidth
disabled={isUserLookupLoading}
/>
<Button
Icon={IconSearch}
variant="primary"
accent="blue"
title={t`Search`}
onClick={handleSearch}
disabled={!userIdentifier.trim() || isUserLookupLoading}
/>
</StyledContainer>
</Section>
)}
{isDefined(userLookupResult) && (
<>
<Section>
<H2Title title={t`User Info`} description={t`About this user`} />
<SettingsTableCard
items={userInfoItems}
rounded
gridAutoColumns="1fr 4fr"
<H2Title
title={t`Recent Users`}
description={
canManageFeatureFlags
? t`Last 10 users created. Click to manage feature flags or impersonate.`
: t`Last 10 users created. Click to impersonate.`
}
/>
<SettingsTextInput
instanceId="admin-panel-user-search"
value={userSearchTerm}
onChange={setUserSearchTerm}
placeholder={t`Search by name, email, or user ID...`}
fullWidth
/>
{recentUsers.length === 0 ? (
<StyledEmptyState>
{isLoadingUsers
? t`Loading...`
: t`No users found matching your search criteria.`}
</StyledEmptyState>
) : (
<Table>
<TableBody>
<TableRow gridTemplateColumns="1fr 2fr 1fr">
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Email`}</TableHeader>
<TableHeader align="right">{t`Workspace`}</TableHeader>
</TableRow>
{recentUsers.map((user) => (
<TableRow
key={user.id}
gridTemplateColumns="1fr 2fr 1fr"
to={getSettingsPath(SettingsPath.AdminPanelUserDetail, {
userId: user.id,
})}
>
<TableCell color={themeCssVariables.font.color.primary}>
{`${user.firstName || ''} ${user.lastName || ''}`.trim() ||
'\u2014'}
</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell align="right">
{user.workspaceName || '\u2014'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</Section>
<Section>
<H2Title
title={t`Workspaces`}
description={t`All workspaces this user is a member of`}
title={t`Top Workspaces`}
description={t`Top 10 workspaces by number of users`}
/>
<TabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID}
<SettingsTextInput
instanceId="admin-panel-workspace-search"
value={workspaceSearchTerm}
onChange={setWorkspaceSearchTerm}
placeholder={t`Search by workspace name, subdomain, or ID...`}
fullWidth
/>
<SettingsAdminWorkspaceContent activeWorkspace={activeWorkspace} />
{topWorkspaces.length === 0 ? (
<StyledEmptyState>
{isLoadingWorkspaces
? t`Loading...`
: t`No workspaces found matching your search criteria.`}
</StyledEmptyState>
) : (
<Table>
<TableBody>
<TableRow gridTemplateColumns="2fr 1fr">
<TableHeader>{t`Workspace`}</TableHeader>
<TableHeader align="right">{t`Users`}</TableHeader>
</TableRow>
{topWorkspaces.map((workspace) => (
<TableRow
key={workspace.id}
gridTemplateColumns="2fr 1fr"
to={getSettingsPath(
SettingsPath.AdminPanelWorkspaceDetail,
{ workspaceId: workspace.id },
)}
>
<TableCell color={themeCssVariables.font.color.primary}>
{workspace.name || '\u2014'}
</TableCell>
<TableCell align="right">
{workspace.totalUsers}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</Section>
</>
)}
@@ -1,46 +1,29 @@
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { SettingsTableCard } from '@/settings/components/SettingsTableCard';
import { useFeatureFlagState } from '@/settings/admin-panel/hooks/useFeatureFlagState';
import { useImpersonationAuth } from '@/settings/admin-panel/hooks/useImpersonationAuth';
import { useImpersonationRedirect } from '@/settings/admin-panel/hooks/useImpersonationRedirect';
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
import { type WorkspaceInfo } from '@/settings/admin-panel/types/WorkspaceInfo';
import { getWorkspaceSchemaName } from '@/settings/admin-panel/utils/getWorkspaceSchemaName';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useMutation } from '@apollo/client/react';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { useState } from 'react';
import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils';
import { AvatarOrIcon, Chip } from 'twenty-ui/components';
import { SettingsPath } from 'twenty-shared/types';
import {
getImageAbsoluteURI,
getSettingsPath,
isDefined,
} from 'twenty-shared/utils';
import { AvatarOrIcon, LinkChip } from 'twenty-ui/components';
import {
H2Title,
IconEyeShare,
IconCalendar,
IconHome,
IconId,
IconLink,
IconStatusChange,
IconUser,
} from 'twenty-ui/display';
import { Button, Toggle } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import {
type FeatureFlagKey,
ImpersonateDocument,
UpdateWorkspaceFeatureFlagDocument,
} from '~/generated-metadata/graphql';
type SettingsAdminWorkspaceContentProps = {
activeWorkspace: WorkspaceInfo | undefined;
@@ -53,93 +36,11 @@ const StyledContainer = styled.div`
margin-top: ${themeCssVariables.spacing[6]};
`;
const StyledButtonContainer = styled.div`
margin-top: ${themeCssVariables.spacing[3]};
`;
export const SettingsAdminWorkspaceContent = ({
activeWorkspace,
}: SettingsAdminWorkspaceContentProps) => {
const canManageFeatureFlags = useAtomStateValue(canManageFeatureFlagsState);
const { enqueueErrorSnackBar } = useSnackBar();
const [currentUser] = useAtomState(currentUserState);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const [updateFeatureFlag] = useMutation(UpdateWorkspaceFeatureFlagDocument);
const [isImpersonateLoading, setIsImpersonationLoading] = useState(false);
const { executeImpersonationAuth } = useImpersonationAuth();
const { executeImpersonationRedirect } = useImpersonationRedirect();
const [impersonate] = useMutation(ImpersonateDocument);
const { updateFeatureFlagState } = useFeatureFlagState();
const userLookupResult = useAtomStateValue(userLookupResultState);
const { t } = useLingui();
const handleImpersonate = async (workspaceId: string) => {
if (!userLookupResult?.user.id) {
enqueueErrorSnackBar({ message: t`Please search for a user first` });
return;
}
setIsImpersonationLoading(true);
await impersonate({
variables: { userId: userLookupResult.user.id, workspaceId },
onCompleted: async (data) => {
const { loginToken, workspace } = data.impersonate;
const isCurrentWorkspace = workspace.id === currentWorkspace?.id;
if (isCurrentWorkspace) {
await executeImpersonationAuth(loginToken.token);
return;
}
return executeImpersonationRedirect(
workspace.workspaceUrls,
loginToken.token,
'_blank',
);
},
onError: (error) => {
const errorMessage = error.message;
enqueueErrorSnackBar({
message: t`Failed to impersonate user. ${errorMessage}`,
});
},
}).finally(() => {
setIsImpersonationLoading(false);
});
};
const handleFeatureFlagUpdate = async (
workspaceId: string,
featureFlag: FeatureFlagKey,
value: boolean,
) => {
const previousValue = userLookupResult?.workspaces
.find((workspace) => workspace.id === workspaceId)
?.featureFlags.find((flag) => flag.key === featureFlag)?.value;
updateFeatureFlagState(workspaceId, featureFlag, value);
await updateFeatureFlag({
variables: {
workspaceId,
featureFlag,
value,
},
onError: (error) => {
if (isDefined(previousValue)) {
updateFeatureFlagState(workspaceId, featureFlag, previousValue);
}
const errorMessage = error.message;
enqueueErrorSnackBar({
message: t`Failed to update feature flag. ${errorMessage}`,
});
},
});
};
const getWorkspaceUrl = (workspaceUrls: WorkspaceInfo['workspaceUrls']) => {
return workspaceUrls.customUrl ?? workspaceUrls.subdomainUrl;
};
@@ -148,10 +49,13 @@ export const SettingsAdminWorkspaceContent = ({
{
Icon: IconHome,
label: t`Name`,
value: (
<Chip
value: activeWorkspace?.id ? (
<LinkChip
label={activeWorkspace?.name ?? ''}
emptyLabel={t`Untitled`}
to={getSettingsPath(SettingsPath.AdminPanelWorkspaceDetail, {
workspaceId: activeWorkspace.id,
})}
leftComponent={
<AvatarOrIcon
avatarUrl={
@@ -165,6 +69,8 @@ export const SettingsAdminWorkspaceContent = ({
/>
}
/>
) : (
(activeWorkspace?.name ?? '')
),
},
{
@@ -191,6 +97,18 @@ export const SettingsAdminWorkspaceContent = ({
label: t`Members`,
value: activeWorkspace?.totalUsers,
},
{
Icon: IconStatusChange,
label: t`Status`,
value: activeWorkspace?.activationStatus,
},
{
Icon: IconCalendar,
label: t`Created`,
value: activeWorkspace?.createdAt
? new Date(activeWorkspace.createdAt).toLocaleDateString()
: '',
},
];
if (!activeWorkspace) return null;
@@ -206,62 +124,7 @@ export const SettingsAdminWorkspaceContent = ({
items={workspaceInfoItems}
gridAutoColumns="1fr 4fr"
/>
<StyledButtonContainer>
{currentUser?.canImpersonate && (
<Button
Icon={IconEyeShare}
variant="primary"
accent="default"
title={
activeWorkspace.allowImpersonation === false
? t`Impersonation is disabled for this workspace`
: t`Impersonate`
}
onClick={() => handleImpersonate(activeWorkspace.id)}
disabled={
isImpersonateLoading ||
activeWorkspace.allowImpersonation === false
}
dataTestId="impersonate-button"
/>
)}
</StyledButtonContainer>
</Section>
{canManageFeatureFlags && (
<Table>
<TableBody>
<TableRow
gridAutoColumns="1fr 100px"
mobileGridAutoColumns="1fr 80px"
>
<TableHeader>{t`Feature Flag`}</TableHeader>
<TableHeader align="right">{t`Status`}</TableHeader>
</TableRow>
{activeWorkspace.featureFlags.map((flag) => (
<TableRow
gridAutoColumns="1fr 100px"
mobileGridAutoColumns="1fr 80px"
key={flag.key}
>
<TableCell>{flag.key}</TableCell>
<TableCell align="right">
<Toggle
value={flag.value}
onChange={(newValue) =>
handleFeatureFlagUpdate(
activeWorkspace.id,
flag.key,
newValue,
)
}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</StyledContainer>
);
};
@@ -41,11 +41,7 @@ export const ConfigVariableValueInput = ({
options={variable.options}
disabled={disabled}
placeholder={
disabled
? t`Undefined`
: variable.isSensitive
? t`Enter a new secret value`
: t`Enter a value to store in database`
disabled ? t`Undefined` : t`Enter a value to store in database`
}
/>
) : (
@@ -9,19 +9,6 @@ type FormValues = {
value: ConfigVariableValue;
};
const hasMeaningfulValue = (value: ConfigVariableValue): boolean => {
if (value === null || value === undefined) {
return false;
}
if (typeof value === 'string') {
return value.trim() !== '';
}
if (Array.isArray(value)) {
return value.length > 0;
}
return true;
};
export const useConfigVariableForm = (variable?: ConfigVariable) => {
const validationSchema = z.object({
value: z.union([
@@ -35,10 +22,9 @@ export const useConfigVariableForm = (variable?: ConfigVariable) => {
});
const {
control,
handleSubmit,
reset,
formState: { isSubmitting, isDirty },
setValue,
formState: { isSubmitting },
watch,
} = useForm<FormValues>({
resolver: zodResolver(validationSchema),
@@ -46,19 +32,27 @@ export const useConfigVariableForm = (variable?: ConfigVariable) => {
});
const currentValue = watch('value');
const isValueValid =
variable !== undefined &&
const hasValueChanged = currentValue !== variable?.value;
const isValueValid = !!(
variable &&
!variable.isEnvOnly &&
isDirty &&
hasMeaningfulValue(currentValue);
hasValueChanged &&
((typeof currentValue === 'string' && currentValue.trim() !== '') ||
typeof currentValue === 'boolean' ||
typeof currentValue === 'number' ||
(Array.isArray(currentValue) && currentValue.length > 0) ||
(typeof currentValue === 'object' &&
currentValue !== null &&
!Array.isArray(currentValue)))
);
return {
control,
handleSubmit,
reset,
setValue,
isSubmitting,
watch,
currentValue,
hasValueChanged: isDirty,
hasValueChanged,
isValueValid,
};
};
@@ -8,12 +8,15 @@ export const USER_LOOKUP_ADMIN_PANEL = gql`
email
firstName
lastName
createdAt
}
workspaces {
id
name
logo
totalUsers
activationStatus
createdAt
allowImpersonation
workspaceUrls {
customUrl
@@ -0,0 +1,15 @@
import { gql } from '@apollo/client';
export const ADMIN_PANEL_RECENT_USERS = gql`
query AdminPanelRecentUsers($searchTerm: String) {
adminPanelRecentUsers(searchTerm: $searchTerm) {
id
email
firstName
lastName
createdAt
workspaceName
workspaceId
}
}
`;
@@ -0,0 +1,12 @@
import { gql } from '@apollo/client';
export const ADMIN_PANEL_TOP_WORKSPACES = gql`
query AdminPanelTopWorkspaces($searchTerm: String) {
adminPanelTopWorkspaces(searchTerm: $searchTerm) {
id
name
totalUsers
subdomain
}
}
`;
@@ -0,0 +1,27 @@
import { gql } from '@apollo/client';
export const GET_ADMIN_CHAT_THREAD_MESSAGES = gql`
query GetAdminChatThreadMessages($workspaceId: String!, $threadId: String!) {
getAdminChatThreadMessages(workspaceId: $workspaceId, threadId: $threadId) {
thread {
id
title
totalInputTokens
totalOutputTokens
conversationSize
createdAt
updatedAt
}
messages {
id
role
parts {
type
textContent
toolName
}
createdAt
}
}
}
`;

Some files were not shown because too many files have changed in this diff Show More