Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a051150bd9 |
@@ -25,7 +25,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 10
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -14,8 +14,8 @@ concurrency:
|
||||
|
||||
env:
|
||||
# restore-cache action adds 'v4-' prefix and '-<branch>-<sha>' suffix to the key
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-depot-ubuntu-24.04-8-runner
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-depot-ubuntu-24.04-8-runner-${{ github.ref_name }}-${{ github.sha }}
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-ubuntu-latest-8-cores-runner
|
||||
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-ubuntu-latest-8-cores-runner-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
REACT_APP_SERVER_BASE_URL: http://localhost:3000
|
||||
steps:
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
|
||||
front-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: front-sb-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
if: false
|
||||
needs: front-sb-build
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
@@ -211,7 +211,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=10240"
|
||||
steps:
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
tasks: ${{ matrix.task }}
|
||||
sdk-e2e-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: [changed-files-check, sdk-test]
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
services:
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
|
||||
server-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: server-setup
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
|
||||
server-integration-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: server-setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
preview-environment:
|
||||
timeout-minutes: 310
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn
|
||||
|
||||
# codegen
|
||||
generated
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# dev
|
||||
/dist/
|
||||
|
||||
.twenty/*
|
||||
!.twenty/output/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
@@ -1 +0,0 @@
|
||||
24.5.0
|
||||
@@ -1 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
@@ -1,9 +0,0 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
|
||||
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
|
||||
@@ -1,51 +0,0 @@
|
||||
This is a [Twenty](https://twenty.com) application project bootstrapped with [`create-twenty-app`](https://www.npmjs.com/package/create-twenty-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, authenticate to your workspace:
|
||||
|
||||
```bash
|
||||
yarn twenty auth:login
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
|
||||
```bash
|
||||
yarn twenty app:dev
|
||||
```
|
||||
|
||||
Open your Twenty instance and go to `/settings/applications` section to see the result.
|
||||
|
||||
## Available Commands
|
||||
|
||||
Run `yarn twenty help` to list all available commands. Common commands:
|
||||
|
||||
```bash
|
||||
# Authentication
|
||||
yarn twenty auth:login # Authenticate with Twenty
|
||||
yarn twenty auth:logout # Remove credentials
|
||||
yarn twenty auth:status # Check auth status
|
||||
yarn twenty auth:switch # Switch default workspace
|
||||
yarn twenty auth:list # List all configured workspaces
|
||||
|
||||
# Application
|
||||
yarn twenty app:dev # Start dev mode (watch, build, sync, and auto-generate typed client)
|
||||
yarn twenty entity:add # Add a new entity (object, field, function, front-component, role, view, navigation-menu-item)
|
||||
yarn twenty function:logs # Stream function logs
|
||||
yarn twenty function:execute # Execute a function with JSON payload
|
||||
yarn twenty app:uninstall # Uninstall app from workspace
|
||||
```
|
||||
|
||||
## LLMs instructions
|
||||
|
||||
Main docs and pitfalls are available in LLMS.md file.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Twenty applications, take a look at the following resources:
|
||||
|
||||
- [twenty-sdk](https://www.npmjs.com/package/twenty-sdk) - learn about `twenty-sdk` tool.
|
||||
- [Twenty doc](https://docs.twenty.com/) - Twenty's documentation.
|
||||
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
|
||||
|
||||
You can check out [the Twenty GitHub repository](https://github.com/twentyhq/twenty) - your feedback and contributions are welcome!
|
||||
@@ -1,29 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default [
|
||||
// Base JS recommended rules
|
||||
js.configs.recommended,
|
||||
|
||||
// TypeScript recommended rules
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Common TypeScript-friendly tweaks
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'no-unused-vars': 'off', // handled by TS rule
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "call-recording",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"twenty": "twenty",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^18.2.0",
|
||||
"eslint": "^9.32.0",
|
||||
"react": "^18.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.50.0"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4daa5147-7e70-4e43-b091-c27e1e8a32e3',
|
||||
displayName: 'Call recording',
|
||||
description: 'Allows to record calls',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const HelloWorld = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>Hello, World!</h1>
|
||||
<p>This is your first front component.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '5c2fd1e8-dd18-4354-9d0e-424beedafde9',
|
||||
name: 'hello-world-front-component',
|
||||
description: 'A sample front component',
|
||||
component: HelloWorld,
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
// TODO: implement end recording logic
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '471353f6-5933-417b-8062-9ad0fc44cd7f',
|
||||
name: 'end-recording',
|
||||
description: 'Endpoint to end a call recording',
|
||||
timeoutSeconds: 30,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/end-recording',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
});
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import { CALL_RECORDING_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/call-recording-view';
|
||||
import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '5248a62d-7d2e-43a7-ba45-6e8f61876a71',
|
||||
name: 'Call recordings',
|
||||
icon: 'IconPhone',
|
||||
position: 0,
|
||||
viewUniversalIdentifier: CALL_RECORDING_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export const CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER =
|
||||
'af251b70-85c6-49bd-bf4a-2631f34c8f1a';
|
||||
|
||||
export const NAME_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'272dca6f-b3aa-49f5-b7ed-39780052f1fe';
|
||||
|
||||
export const CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'c581a044-f646-464b-aa4b-56b8ea9bf05a';
|
||||
|
||||
export const ENDED_AT_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'56185e64-6591-41c1-a3e0-af8de20a5471';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
nameSingular: 'callRecording',
|
||||
namePlural: 'callRecordings',
|
||||
labelSingular: 'Call recording',
|
||||
labelPlural: 'Call recordings',
|
||||
description: 'A recorded call',
|
||||
icon: 'IconPhone',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.TEXT,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
description: 'Name of the call recording',
|
||||
icon: 'IconAbc',
|
||||
},
|
||||
{
|
||||
universalIdentifier: CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.DATE_TIME,
|
||||
name: 'createdAt',
|
||||
label: 'Created at',
|
||||
description: 'When the call recording was created',
|
||||
icon: 'IconCalendar',
|
||||
},
|
||||
{
|
||||
universalIdentifier: ENDED_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.DATE_TIME,
|
||||
name: 'endedAt',
|
||||
label: 'Ended at',
|
||||
description: 'When the call ended',
|
||||
icon: 'IconCalendar',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'f9cfb3ce-cb1e-4f55-af85-be45f6059054';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Call recording default function role',
|
||||
description: 'Call recording default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: false,
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER, CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, ENDED_AT_FIELD_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording';
|
||||
import { defineView } from 'twenty-sdk';
|
||||
|
||||
export const CALL_RECORDING_VIEW_UNIVERSAL_IDENTIFIER =
|
||||
'9c9c09bb-de9f-4248-89f2-e7d91f29c3ed';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: CALL_RECORDING_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
name: 'Call recordings',
|
||||
objectUniversalIdentifier: CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
icon: 'IconPhone',
|
||||
position: 0,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: 'b5609679-8451-45ec-ad52-5a4e3720af45',
|
||||
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
isVisible: true,
|
||||
size: 12,
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'd791bea6-c49e-4d6f-8864-737ed00276f8',
|
||||
fieldMetadataUniversalIdentifier: CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
isVisible: true,
|
||||
size: 12,
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'db96d9cf-cbd8-407b-b748-7b12913f018b',
|
||||
fieldMetadataUniversalIdentifier: ENDED_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
isVisible: true,
|
||||
size: 12,
|
||||
position: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"allowUnreachableCode": false,
|
||||
"strict": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": false,
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"lib": ["es2020", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"src/*": ["./src/*"],
|
||||
"~/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
import { agentChatSelectedFilesStateV2 } from '@/ai/states/agentChatSelectedFilesStateV2';
|
||||
import { agentChatUploadedFilesStateV2 } from '@/ai/states/agentChatUploadedFilesStateV2';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { type FileUIPart } from 'ai';
|
||||
import { buildSignedPath, isDefined } from 'twenty-shared/utils';
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useAIChatFileUpload = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
const [uploadFile] = useUploadFileMutation({ client: apolloClient });
|
||||
const coreClient = useApolloCoreClient();
|
||||
const [uploadFile] = useUploadFileMutation({ client: coreClient });
|
||||
const { t } = useLingui();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const [agentChatSelectedFiles, setAgentChatSelectedFiles] = useRecoilStateV2(
|
||||
|
||||
@@ -1,35 +1,39 @@
|
||||
import { aiModelsState } from '@/client-config/states/aiModelsState';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
|
||||
import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel';
|
||||
import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel';
|
||||
import { useWorkspaceAiModelAvailability } from '@/ai/hooks/useWorkspaceAiModelAvailability';
|
||||
import { aiModelsState } from '@/client-config/states/aiModelsState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { getModelProviderLabel } from '~/pages/settings/ai/utils/getModelProviderLabel';
|
||||
import { MODEL_FAMILY_CONFIG } from '~/pages/settings/ai/constants/SettingsAiModelProviders';
|
||||
|
||||
export const useAiModelOptions = (
|
||||
includeDeprecated = false,
|
||||
): SelectOption<string>[] => {
|
||||
const aiModels = useRecoilValueV2(aiModelsState);
|
||||
const { isModelEnabled } = useWorkspaceAiModelAvailability();
|
||||
|
||||
return aiModels
|
||||
.filter(
|
||||
(model) =>
|
||||
(includeDeprecated || !model.deprecated) &&
|
||||
isModelEnabled(model.modelId, model),
|
||||
)
|
||||
.filter((model) => includeDeprecated || !model.deprecated)
|
||||
.map((model) => ({
|
||||
value: model.modelId,
|
||||
label:
|
||||
model.modelId === DEFAULT_FAST_MODEL ||
|
||||
model.modelId === DEFAULT_SMART_MODEL
|
||||
? model.label
|
||||
: `${model.label} (${getModelProviderLabel(model.modelFamily) || model.inferenceProvider})`,
|
||||
: `${model.label} (${getModelFamilyLabel(model.modelFamily) ?? model.inferenceProvider})`,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
};
|
||||
|
||||
const getModelFamilyLabel = (
|
||||
modelFamily: string | null | undefined,
|
||||
): string | undefined => {
|
||||
if (!modelFamily) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return MODEL_FAMILY_CONFIG[modelFamily]?.label || modelFamily;
|
||||
};
|
||||
|
||||
export const useAiModelLabel = (
|
||||
modelId: string | undefined,
|
||||
includeProvider = true,
|
||||
@@ -54,5 +58,5 @@ export const useAiModelLabel = (
|
||||
return model.label;
|
||||
}
|
||||
|
||||
return `${model.label} (${getModelProviderLabel(model.modelFamily) || model.inferenceProvider})`;
|
||||
return `${model.label} (${getModelFamilyLabel(model.modelFamily) ?? model.inferenceProvider})`;
|
||||
};
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel';
|
||||
import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { aiModelsState } from '@/client-config/states/aiModelsState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { type ClientAiModelConfig } from '~/generated-metadata/graphql';
|
||||
|
||||
const VIRTUAL_MODEL_IDS: Set<string> = new Set([
|
||||
DEFAULT_SMART_MODEL,
|
||||
DEFAULT_FAST_MODEL,
|
||||
]);
|
||||
|
||||
const isVirtualModel = (modelId: string) => VIRTUAL_MODEL_IDS.has(modelId);
|
||||
|
||||
export const useWorkspaceAiModelAvailability = () => {
|
||||
const aiModels = useRecoilValueV2(aiModelsState);
|
||||
const currentWorkspace = useRecoilValueV2(currentWorkspaceState);
|
||||
|
||||
const useRecommendedModels = currentWorkspace?.useRecommendedModels ?? true;
|
||||
const autoEnableNewAiModels = currentWorkspace?.autoEnableNewAiModels ?? true;
|
||||
const disabledAiModelIds = currentWorkspace?.disabledAiModelIds ?? [];
|
||||
const enabledAiModelIds = currentWorkspace?.enabledAiModelIds ?? [];
|
||||
|
||||
const isModelEnabled = (
|
||||
modelId: string,
|
||||
model?: ClientAiModelConfig,
|
||||
): boolean => {
|
||||
if (isVirtualModel(modelId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (useRecommendedModels) {
|
||||
return model?.isRecommended === true;
|
||||
}
|
||||
|
||||
return autoEnableNewAiModels
|
||||
? !disabledAiModelIds.includes(modelId)
|
||||
: enabledAiModelIds.includes(modelId);
|
||||
};
|
||||
|
||||
const realModels = aiModels.filter(
|
||||
(model) => !isVirtualModel(model.modelId) && !model.deprecated,
|
||||
);
|
||||
|
||||
const enabledModels = realModels.filter((model) =>
|
||||
isModelEnabled(model.modelId, model),
|
||||
);
|
||||
|
||||
const allModelsWithAvailability = realModels.map((model) => ({
|
||||
...model,
|
||||
isEnabled: isModelEnabled(model.modelId, model),
|
||||
}));
|
||||
|
||||
return {
|
||||
isModelEnabled,
|
||||
enabledModels,
|
||||
realModels,
|
||||
allModelsWithAvailability,
|
||||
useRecommendedModels,
|
||||
autoEnableNewAiModels,
|
||||
disabledAiModelIds,
|
||||
enabledAiModelIds,
|
||||
};
|
||||
};
|
||||
@@ -70,10 +70,6 @@ const mockWorkspace = {
|
||||
fastModel: DEFAULT_FAST_MODEL,
|
||||
smartModel: DEFAULT_SMART_MODEL,
|
||||
routerModel: 'auto',
|
||||
autoEnableNewAiModels: true,
|
||||
disabledAiModelIds: [],
|
||||
enabledAiModelIds: [],
|
||||
useRecommendedModels: true,
|
||||
workspaceCustomApplication: CUSTOM_WORKSPACE_APPLICATION_MOCK,
|
||||
workspaceCustomApplicationId: CUSTOM_WORKSPACE_APPLICATION_MOCK.id,
|
||||
};
|
||||
|
||||
@@ -38,10 +38,6 @@ export type CurrentWorkspace = Pick<
|
||||
| 'smartModel'
|
||||
| 'aiAdditionalInstructions'
|
||||
| 'editableProfileFields'
|
||||
| 'autoEnableNewAiModels'
|
||||
| 'disabledAiModelIds'
|
||||
| 'enabledAiModelIds'
|
||||
| 'useRecommendedModels'
|
||||
> & {
|
||||
defaultRole?: Omit<Role, 'workspaceMembers' | 'agents' | 'apiKeys'> | null;
|
||||
workspaceCustomApplication: Pick<Application, 'id'> | null;
|
||||
|
||||
+7
@@ -18,6 +18,13 @@ export const IsAppMetadataReadyEffect = () => {
|
||||
const viewsEntry = useFamilyRecoilValueV2(metadataStoreState, 'views');
|
||||
const setIsAppMetadataReady = useSetRecoilStateV2(isAppMetadataReadyState);
|
||||
|
||||
console.log('objectsEntry', objectsEntry);
|
||||
console.log('viewsEntry', viewsEntry);
|
||||
console.log('isLoggedIn', isLoggedIn);
|
||||
console.log('currentUser', currentUser);
|
||||
console.log('currentWorkspace', currentWorkspace);
|
||||
console.log('setIsAppMetadataReady', setIsAppMetadataReady);
|
||||
|
||||
useEffect(() => {
|
||||
const hasActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
|
||||
|
||||
|
||||
-4
@@ -71,10 +71,6 @@ describe('useColumnDefinitionsFromObjectMetadata', () => {
|
||||
eventLogRetentionDays: 365 * 3,
|
||||
fastModel: DEFAULT_FAST_MODEL,
|
||||
smartModel: DEFAULT_SMART_MODEL,
|
||||
autoEnableNewAiModels: true,
|
||||
disabledAiModelIds: [],
|
||||
enabledAiModelIds: [],
|
||||
useRecommendedModels: true,
|
||||
});
|
||||
|
||||
const companyObjectMetadata = generatedMockObjectMetadataItems.find(
|
||||
|
||||
+8
-7
@@ -6,8 +6,8 @@ import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailab
|
||||
|
||||
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
|
||||
import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useColumnDefinitionsFromObjectMetadata = (
|
||||
@@ -28,12 +28,13 @@ export const useColumnDefinitionsFromObjectMetadata = (
|
||||
);
|
||||
|
||||
const columnDefinitions: ColumnDefinition<FieldMetadata>[] = useMemo(() => {
|
||||
const activeFieldMetadataItems = objectMetadataItem.readableFields.filter(
|
||||
(field) =>
|
||||
field.isActive &&
|
||||
(!isHiddenSystemField(field) ||
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId),
|
||||
);
|
||||
const activeFieldMetadataItems =
|
||||
objectMetadataItem.readableFields.filter(
|
||||
(field) =>
|
||||
field.isActive &&
|
||||
(!isHiddenSystemField(field) ||
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId),
|
||||
);
|
||||
|
||||
return activeFieldMetadataItems
|
||||
.map((field, index) =>
|
||||
|
||||
-233
@@ -1,233 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
|
||||
import { GET_ADMIN_AI_MODELS } from '@/settings/admin-panel/ai/graphql/queries/getAdminAiModels';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
H2Title,
|
||||
IconArchive,
|
||||
IconFilter,
|
||||
IconPlug,
|
||||
IconRobot,
|
||||
IconSearch,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import {
|
||||
useCreateDatabaseConfigVariableMutation,
|
||||
useGetAdminAiModelsQuery,
|
||||
useSetAdminAiModelEnabledMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { getModelIcon } from '~/pages/settings/ai/utils/getModelIcon';
|
||||
import { getModelProviderLabel } from '~/pages/settings/ai/utils/getModelProviderLabel';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const SettingsAdminAI = () => {
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [showUnconfigured, setShowUnconfigured] = useState(false);
|
||||
const [showDeprecated, setShowDeprecated] = useState(false);
|
||||
const { refetch: refetchClientConfig } = useClientConfig();
|
||||
|
||||
const { data } = useGetAdminAiModelsQuery();
|
||||
const [createConfigVariable] = useCreateDatabaseConfigVariableMutation();
|
||||
const [setModelEnabled] = useSetAdminAiModelEnabledMutation();
|
||||
|
||||
const autoEnableNewModels =
|
||||
data?.getAdminAiModels?.autoEnableNewModels ?? true;
|
||||
|
||||
const models = data?.getAdminAiModels?.models ?? [];
|
||||
|
||||
const handleAutoEnableToggle = async (checked: boolean) => {
|
||||
try {
|
||||
await createConfigVariable({
|
||||
variables: {
|
||||
key: 'AI_AUTO_ENABLE_NEW_MODELS',
|
||||
value: checked,
|
||||
},
|
||||
refetchQueries: [{ query: GET_ADMIN_AI_MODELS }],
|
||||
});
|
||||
|
||||
await refetchClientConfig();
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to update auto-enable setting`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelToggle = async (
|
||||
modelId: string,
|
||||
isCurrentlyEnabled: boolean,
|
||||
) => {
|
||||
try {
|
||||
await setModelEnabled({
|
||||
variables: {
|
||||
modelId,
|
||||
enabled: !isCurrentlyEnabled,
|
||||
},
|
||||
refetchQueries: [{ query: GET_ADMIN_AI_MODELS }],
|
||||
});
|
||||
|
||||
await refetchClientConfig();
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to update model availability`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let filteredModels = models;
|
||||
|
||||
if (!showUnconfigured) {
|
||||
filteredModels = filteredModels.filter((model) => model.isAvailable);
|
||||
}
|
||||
|
||||
if (!showDeprecated) {
|
||||
filteredModels = filteredModels.filter((model) => !model.deprecated);
|
||||
}
|
||||
|
||||
if (searchQuery.trim().length > 0) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
|
||||
filteredModels = filteredModels.filter(
|
||||
(model) =>
|
||||
model.label.toLowerCase().includes(query) ||
|
||||
(model.modelFamily?.toLowerCase().includes(query) ?? false) ||
|
||||
model.inferenceProvider.toLowerCase().includes(query),
|
||||
);
|
||||
}
|
||||
|
||||
const getModelDescription = (
|
||||
modelFamily: string | null | undefined,
|
||||
isAvailable: boolean,
|
||||
isDeprecated: boolean | null | undefined,
|
||||
) => {
|
||||
const providerLabel = getModelProviderLabel(modelFamily);
|
||||
|
||||
if (isDeprecated === true) {
|
||||
return providerLabel ? t`${providerLabel} — Deprecated` : t`Deprecated`;
|
||||
}
|
||||
|
||||
if (!isAvailable) {
|
||||
return providerLabel
|
||||
? t`${providerLabel} — API key not configured`
|
||||
: t`API key not configured`;
|
||||
}
|
||||
|
||||
return providerLabel;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Admin Model Controls`}
|
||||
description={t`Server-wide AI model availability settings`}
|
||||
/>
|
||||
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconRobot}
|
||||
title={t`Automatically enable new models`}
|
||||
description={t`When enabled, newly added models are available to all workspaces by default`}
|
||||
checked={autoEnableNewModels}
|
||||
onChange={handleAutoEnableToggle}
|
||||
/>
|
||||
</Card>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`All Models`}
|
||||
description={t`Toggle model availability across all workspaces`}
|
||||
/>
|
||||
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="admin-model-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
<Dropdown
|
||||
dropdownId="admin-ai-models-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconFilter}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Filter`}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconPlug}
|
||||
onToggleChange={() =>
|
||||
setShowUnconfigured(!showUnconfigured)
|
||||
}
|
||||
toggled={showUnconfigured}
|
||||
text={t`Unconfigured models`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() => setShowDeprecated(!showDeprecated)}
|
||||
toggled={showDeprecated}
|
||||
text={t`Deprecated models`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</StyledSearchAndFilterContainer>
|
||||
|
||||
<Card rounded>
|
||||
{filteredModels.map((model, index) => (
|
||||
<SettingsOptionCardContentToggle
|
||||
key={model.modelId}
|
||||
Icon={getModelIcon(model.modelFamily)}
|
||||
title={model.label}
|
||||
description={getModelDescription(
|
||||
model.modelFamily,
|
||||
model.isAvailable,
|
||||
model.deprecated,
|
||||
)}
|
||||
checked={model.isAdminEnabled}
|
||||
onChange={() =>
|
||||
handleModelToggle(model.modelId, model.isAdminEnabled)
|
||||
}
|
||||
disabled={!model.isAvailable || model.deprecated === true}
|
||||
divider={index < filteredModels.length - 1}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const SET_ADMIN_AI_MODEL_ENABLED = gql`
|
||||
mutation SetAdminAiModelEnabled($modelId: String!, $enabled: Boolean!) {
|
||||
setAdminAiModelEnabled(modelId: $modelId, enabled: $enabled)
|
||||
}
|
||||
`;
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_ADMIN_AI_MODELS = gql`
|
||||
query GetAdminAiModels {
|
||||
getAdminAiModels {
|
||||
autoEnableNewModels
|
||||
models {
|
||||
modelId
|
||||
label
|
||||
modelFamily
|
||||
inferenceProvider
|
||||
isAvailable
|
||||
isAdminEnabled
|
||||
deprecated
|
||||
isRecommended
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
+3
-14
@@ -4,12 +4,7 @@ import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAd
|
||||
import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
IconHeart,
|
||||
IconSettings2,
|
||||
IconSparkles,
|
||||
IconVariable,
|
||||
} from 'twenty-ui/display';
|
||||
import { IconHeart, IconSettings2, IconVariable } from 'twenty-ui/display';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
|
||||
export const SettingsAdminContent = () => {
|
||||
@@ -24,21 +19,15 @@ export const SettingsAdminContent = () => {
|
||||
Icon: IconSettings2,
|
||||
disabled: !canAccessFullAdminPanel && !canImpersonate,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.AI,
|
||||
title: t`AI`,
|
||||
Icon: IconSparkles,
|
||||
disabled: !canAccessFullAdminPanel,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.CONFIG_VARIABLES,
|
||||
title: t`Config`,
|
||||
title: t`Config Variables`,
|
||||
Icon: IconVariable,
|
||||
disabled: !canAccessFullAdminPanel,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.HEALTH_STATUS,
|
||||
title: t`Health`,
|
||||
title: t`Health Status`,
|
||||
Icon: IconHeart,
|
||||
disabled: !canAccessFullAdminPanel,
|
||||
},
|
||||
|
||||
-3
@@ -1,4 +1,3 @@
|
||||
import { SettingsAdminAI } from '@/settings/admin-panel/ai/components/SettingsAdminAI';
|
||||
import { SettingsAdminGeneral } from '@/settings/admin-panel/components/SettingsAdminGeneral';
|
||||
import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables';
|
||||
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
||||
@@ -16,8 +15,6 @@ export const SettingsAdminTabContent = () => {
|
||||
switch (activeTabId) {
|
||||
case SETTINGS_ADMIN_TABS.GENERAL:
|
||||
return <SettingsAdminGeneral />;
|
||||
case SETTINGS_ADMIN_TABS.AI:
|
||||
return <SettingsAdminAI />;
|
||||
case SETTINGS_ADMIN_TABS.CONFIG_VARIABLES:
|
||||
return <SettingsAdminConfigVariables />;
|
||||
case SETTINGS_ADMIN_TABS.HEALTH_STATUS:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const SETTINGS_ADMIN_TABS = {
|
||||
GENERAL: 'general',
|
||||
AI: 'ai',
|
||||
CONFIG_VARIABLES: 'config-variables',
|
||||
HEALTH_STATUS: 'health-status',
|
||||
};
|
||||
|
||||
+2
-7
@@ -3,7 +3,6 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { getFilterTypeFromFieldType } from 'twenty-shared/utils';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { AdvancedFilterFieldSelectSearchInput } from '@/object-record/advanced-filter/components/AdvancedFilterFieldSelectSearchInput';
|
||||
import { useAdvancedFilterFieldSelectDropdown } from '@/object-record/advanced-filter/hooks/useAdvancedFilterFieldSelectDropdown';
|
||||
@@ -27,7 +26,6 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useContext } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
type SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectFieldMenuProps =
|
||||
{
|
||||
@@ -61,12 +59,9 @@ export const SettingsRolePermissionsObjectLevelRecordLevelPermissionFieldSelectF
|
||||
fieldMetadataItem.label
|
||||
.toLocaleLowerCase()
|
||||
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase()) &&
|
||||
(RECORD_LEVEL_PERMISSION_PREDICATE_FIELD_TYPES.includes(
|
||||
RECORD_LEVEL_PERMISSION_PREDICATE_FIELD_TYPES.includes(
|
||||
fieldMetadataItem.type,
|
||||
) ||
|
||||
(fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
||||
fieldMetadataItem.relation?.targetObjectMetadata.nameSingular ===
|
||||
CoreObjectNameSingular.WorkspaceMember)),
|
||||
),
|
||||
)
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
|
||||
+11
@@ -133,6 +133,17 @@ export const SettingsRolePermissionsObjectLevelRecordLevelPermissionMeValueSelec
|
||||
}
|
||||
|
||||
if (field.type === targetFieldType) {
|
||||
if (
|
||||
targetFieldType === FieldMetadataType.RELATION &&
|
||||
isDefined(selectedFieldMetadataItem)
|
||||
) {
|
||||
return (
|
||||
field.relation?.targetObjectMetadata.nameSingular ===
|
||||
selectedFieldMetadataItem.relation?.targetObjectMetadata
|
||||
.nameSingular
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -14,4 +14,5 @@ export const RECORD_LEVEL_PERMISSION_PREDICATE_FIELD_TYPES = [
|
||||
FieldMetadataType.EMAILS,
|
||||
FieldMetadataType.FULL_NAME,
|
||||
FieldMetadataType.ADDRESS,
|
||||
FieldMetadataType.RELATION,
|
||||
];
|
||||
|
||||
+3
-12
@@ -1,14 +1,10 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RecordFilterGroupLogicalOperator,
|
||||
} from 'twenty-shared/types';
|
||||
import { RecordFilterGroupLogicalOperator } from 'twenty-shared/types';
|
||||
import { getFilterTypeFromFieldType, isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
|
||||
import { useGetDefaultFieldMetadataItemForFilter } from '@/object-record/advanced-filter/hooks/useGetDefaultFieldMetadataItemForFilter';
|
||||
@@ -60,13 +56,8 @@ export const useRecordLevelPermissionFilterActions = ({
|
||||
const getDefaultFieldMetadataItemForRLS = () => {
|
||||
const availableFieldMetadataItemsForRls =
|
||||
availableFieldMetadataItemsForFilter.filter((fieldMetadataItem) => {
|
||||
return (
|
||||
RECORD_LEVEL_PERMISSION_PREDICATE_FIELD_TYPES.includes(
|
||||
fieldMetadataItem.type,
|
||||
) ||
|
||||
(fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
||||
fieldMetadataItem.relation?.targetObjectMetadata.nameSingular ===
|
||||
CoreObjectNameSingular.WorkspaceMember)
|
||||
return RECORD_LEVEL_PERMISSION_PREDICATE_FIELD_TYPES.includes(
|
||||
fieldMetadataItem.type,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -90,10 +90,6 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
fastModel
|
||||
smartModel
|
||||
aiAdditionalInstructions
|
||||
autoEnableNewAiModels
|
||||
disabledAiModelIds
|
||||
enabledAiModelIds
|
||||
useRecommendedModels
|
||||
isTwoFactorAuthenticationEnforced
|
||||
trashRetentionDays
|
||||
eventLogRetentionDays
|
||||
|
||||
@@ -1,388 +1,108 @@
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel';
|
||||
import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel';
|
||||
import { useWorkspaceAiModelAvailability } from '@/ai/hooks/useWorkspaceAiModelAvailability';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { aiModelsState } from '@/client-config/states/aiModelsState';
|
||||
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
H2Title,
|
||||
IconBolt,
|
||||
IconRobot,
|
||||
IconSearch,
|
||||
IconTwentyStar,
|
||||
} from 'twenty-ui/display';
|
||||
import { H2Title, IconBolt } from 'twenty-ui/display';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql';
|
||||
import { getModelIcon } from '~/pages/settings/ai/utils/getModelIcon';
|
||||
import { getModelProviderLabel } from '~/pages/settings/ai/utils/getModelProviderLabel';
|
||||
import { MODEL_FAMILY_CONFIG } from '~/pages/settings/ai/constants/SettingsAiModelProviders';
|
||||
|
||||
const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
width: 100%;
|
||||
`;
|
||||
const VIRTUAL_MODEL_IDS: Set<string> = new Set([
|
||||
DEFAULT_SMART_MODEL,
|
||||
DEFAULT_FAST_MODEL,
|
||||
]);
|
||||
|
||||
export const SettingsAIModelsTab = () => {
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2(
|
||||
currentWorkspaceState,
|
||||
);
|
||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const aiModels = useRecoilValueV2(aiModelsState);
|
||||
|
||||
const {
|
||||
allModelsWithAvailability,
|
||||
enabledModels,
|
||||
useRecommendedModels,
|
||||
autoEnableNewAiModels,
|
||||
realModels,
|
||||
} = useWorkspaceAiModelAvailability();
|
||||
const realModels = aiModels.filter(
|
||||
(model) => !VIRTUAL_MODEL_IDS.has(model.modelId),
|
||||
);
|
||||
|
||||
const currentSmartModel = currentWorkspace?.smartModel;
|
||||
const currentFastModel = currentWorkspace?.fastModel;
|
||||
|
||||
const buildVirtualModelOption = (virtualModelId: string) => {
|
||||
const virtualModel = aiModels.find(
|
||||
(model) => model.modelId === virtualModelId,
|
||||
);
|
||||
const defaultModelOptions = realModels
|
||||
.filter((model) => !model.deprecated || model.modelId === currentSmartModel)
|
||||
.map((model) => ({
|
||||
value: model.modelId,
|
||||
label: model.label,
|
||||
Icon: (model.modelFamily
|
||||
? (MODEL_FAMILY_CONFIG[model.modelFamily] ??
|
||||
MODEL_FAMILY_CONFIG.FALLBACK)
|
||||
: MODEL_FAMILY_CONFIG.FALLBACK
|
||||
).Icon,
|
||||
}));
|
||||
|
||||
return virtualModel
|
||||
? {
|
||||
value: virtualModelId,
|
||||
label: virtualModel.label,
|
||||
Icon: IconTwentyStar,
|
||||
}
|
||||
: null;
|
||||
};
|
||||
|
||||
const smartAutoOption = buildVirtualModelOption(DEFAULT_SMART_MODEL);
|
||||
const fastAutoOption = buildVirtualModelOption(DEFAULT_FAST_MODEL);
|
||||
|
||||
const modelOptions = enabledModels.map((model) => ({
|
||||
value: model.modelId,
|
||||
label: model.label,
|
||||
Icon: getModelIcon(model.modelFamily),
|
||||
}));
|
||||
|
||||
const smartModelOptions = [...modelOptions];
|
||||
|
||||
if (smartAutoOption !== null) {
|
||||
smartModelOptions.unshift(smartAutoOption);
|
||||
}
|
||||
|
||||
const fastModelOptions = [...modelOptions];
|
||||
|
||||
if (fastAutoOption !== null) {
|
||||
fastModelOptions.unshift(fastAutoOption);
|
||||
}
|
||||
|
||||
const handleModelFieldChange = async (
|
||||
field: 'smartModel' | 'fastModel',
|
||||
value: string,
|
||||
) => {
|
||||
const handleDefaultModelChange = async (value: string) => {
|
||||
if (!currentWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousValue = currentWorkspace[field];
|
||||
const previousSmartModel = currentWorkspace.smartModel;
|
||||
|
||||
try {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
[field]: value,
|
||||
smartModel: value,
|
||||
});
|
||||
|
||||
await updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
[field]: value,
|
||||
smartModel: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Default model updated successfully`,
|
||||
});
|
||||
} catch {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
[field]: previousValue,
|
||||
smartModel: previousSmartModel,
|
||||
});
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to update model`,
|
||||
message: t`Failed to update default model`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUseRecommendedToggle = async (checked: boolean) => {
|
||||
if (!currentWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousValue = currentWorkspace.useRecommendedModels;
|
||||
|
||||
let newEnabledIds = currentWorkspace.enabledAiModelIds ?? [];
|
||||
|
||||
if (!checked && previousValue) {
|
||||
const recommendedModelIds = realModels
|
||||
.filter((model) => model.isRecommended)
|
||||
.map((model) => model.modelId);
|
||||
|
||||
newEnabledIds = recommendedModelIds;
|
||||
}
|
||||
|
||||
try {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
useRecommendedModels: checked,
|
||||
enabledAiModelIds: newEnabledIds,
|
||||
});
|
||||
|
||||
await updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
useRecommendedModels: checked,
|
||||
enabledAiModelIds: newEnabledIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
useRecommendedModels: previousValue,
|
||||
});
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to update model selection mode`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleAutoEnableToggle = async (checked: boolean) => {
|
||||
if (!currentWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousAutoEnable = currentWorkspace.autoEnableNewAiModels;
|
||||
const previousDisabled = currentWorkspace.disabledAiModelIds ?? [];
|
||||
const previousEnabled = currentWorkspace.enabledAiModelIds ?? [];
|
||||
|
||||
let newDisabledIds: string[] = [];
|
||||
let newEnabledIds: string[] = [];
|
||||
|
||||
if (checked) {
|
||||
newDisabledIds = realModels
|
||||
.filter((model) => !previousEnabled.includes(model.modelId))
|
||||
.map((model) => model.modelId);
|
||||
newEnabledIds = [];
|
||||
} else {
|
||||
newEnabledIds = realModels
|
||||
.filter((model) => !previousDisabled.includes(model.modelId))
|
||||
.map((model) => model.modelId);
|
||||
newDisabledIds = [];
|
||||
}
|
||||
|
||||
try {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
autoEnableNewAiModels: checked,
|
||||
disabledAiModelIds: newDisabledIds,
|
||||
enabledAiModelIds: newEnabledIds,
|
||||
});
|
||||
|
||||
await updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
autoEnableNewAiModels: checked,
|
||||
disabledAiModelIds: newDisabledIds,
|
||||
enabledAiModelIds: newEnabledIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
autoEnableNewAiModels: previousAutoEnable,
|
||||
disabledAiModelIds: previousDisabled,
|
||||
enabledAiModelIds: previousEnabled,
|
||||
});
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to update model availability settings`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelToggle = async (
|
||||
modelId: string,
|
||||
isCurrentlyEnabled: boolean,
|
||||
) => {
|
||||
if (!currentWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousDisabled = currentWorkspace.disabledAiModelIds ?? [];
|
||||
const previousEnabled = currentWorkspace.enabledAiModelIds ?? [];
|
||||
|
||||
let newDisabledIds = [...previousDisabled];
|
||||
let newEnabledIds = [...previousEnabled];
|
||||
|
||||
if (autoEnableNewAiModels) {
|
||||
if (isCurrentlyEnabled) {
|
||||
newDisabledIds = [...previousDisabled, modelId];
|
||||
} else {
|
||||
newDisabledIds = previousDisabled.filter((id) => id !== modelId);
|
||||
}
|
||||
} else {
|
||||
if (isCurrentlyEnabled) {
|
||||
newEnabledIds = previousEnabled.filter((id) => id !== modelId);
|
||||
} else {
|
||||
newEnabledIds = [...previousEnabled, modelId];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
disabledAiModelIds: newDisabledIds,
|
||||
enabledAiModelIds: newEnabledIds,
|
||||
});
|
||||
|
||||
await updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
disabledAiModelIds: newDisabledIds,
|
||||
enabledAiModelIds: newEnabledIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
disabledAiModelIds: previousDisabled,
|
||||
enabledAiModelIds: previousEnabled,
|
||||
});
|
||||
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to update model availability`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const filteredModels = searchQuery.trim()
|
||||
? allModelsWithAvailability.filter((model) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
|
||||
return (
|
||||
model.label.toLowerCase().includes(query) ||
|
||||
(model.modelFamily?.toLowerCase().includes(query) ?? false) ||
|
||||
model.inferenceProvider.toLowerCase().includes(query)
|
||||
);
|
||||
})
|
||||
: allModelsWithAvailability;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Models`}
|
||||
description={t`Configure default AI models and availability`}
|
||||
/>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Default`}
|
||||
description={t`Configure your default AI model`}
|
||||
/>
|
||||
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentSelect
|
||||
Icon={IconBolt}
|
||||
title={t`Smart Model`}
|
||||
description={t`Used for chats, agents, and complex reasoning`}
|
||||
>
|
||||
<Select
|
||||
dropdownId="smart-model-select"
|
||||
value={currentSmartModel}
|
||||
onChange={(value) => handleModelFieldChange('smartModel', value)}
|
||||
options={smartModelOptions}
|
||||
selectSizeVariant="small"
|
||||
dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
|
||||
/>
|
||||
</SettingsOptionCardContentSelect>
|
||||
<SettingsOptionCardContentSelect
|
||||
Icon={IconBolt}
|
||||
title={t`Fast Model`}
|
||||
description={t`Used for lightweight tasks like title generation`}
|
||||
>
|
||||
<Select
|
||||
dropdownId="fast-model-select"
|
||||
value={currentFastModel}
|
||||
onChange={(value) => handleModelFieldChange('fastModel', value)}
|
||||
options={fastModelOptions}
|
||||
selectSizeVariant="small"
|
||||
dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
|
||||
/>
|
||||
</SettingsOptionCardContentSelect>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconTwentyStar}
|
||||
title={t`Use best models only`}
|
||||
description={t`Restrict available models to a curated list`}
|
||||
checked={useRecommendedModels}
|
||||
onChange={handleUseRecommendedToggle}
|
||||
divider={!useRecommendedModels}
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentSelect
|
||||
Icon={IconBolt}
|
||||
title={t`Default Model`}
|
||||
description={t`Default model for new chats and agents`}
|
||||
>
|
||||
<Select
|
||||
dropdownId="default-model-select"
|
||||
value={currentSmartModel}
|
||||
onChange={handleDefaultModelChange}
|
||||
options={defaultModelOptions}
|
||||
selectSizeVariant="small"
|
||||
/>
|
||||
{!useRecommendedModels && (
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconRobot}
|
||||
title={t`Automatically mark new models as available`}
|
||||
description={t`When enabled, new AI models will be available to users by default`}
|
||||
checked={autoEnableNewAiModels}
|
||||
onChange={handleAutoEnableToggle}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</Section>
|
||||
|
||||
{!useRecommendedModels && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Available`}
|
||||
description={t`Models available in the chat model picker`}
|
||||
/>
|
||||
|
||||
<StyledSearchContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="model-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
|
||||
<Card rounded>
|
||||
{filteredModels.map((model, index) => (
|
||||
<SettingsOptionCardContentToggle
|
||||
key={model.modelId}
|
||||
Icon={getModelIcon(model.modelFamily)}
|
||||
title={model.label}
|
||||
description={getModelProviderLabel(model.modelFamily)}
|
||||
checked={model.isEnabled}
|
||||
onChange={() =>
|
||||
handleModelToggle(model.modelId, model.isEnabled)
|
||||
}
|
||||
divider={index < filteredModels.length - 1}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
</Section>
|
||||
)}
|
||||
</>
|
||||
</SettingsOptionCardContentSelect>
|
||||
</Card>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
|
||||
import { MODEL_FAMILY_CONFIG } from '~/pages/settings/ai/constants/SettingsAiModelProviders';
|
||||
|
||||
export const getModelIcon = (
|
||||
modelFamily: string | null | undefined,
|
||||
): IconComponent => {
|
||||
if (!modelFamily) {
|
||||
return MODEL_FAMILY_CONFIG.FALLBACK.Icon;
|
||||
}
|
||||
|
||||
const key = modelFamily.toLowerCase();
|
||||
|
||||
return MODEL_FAMILY_CONFIG[key]?.Icon ?? MODEL_FAMILY_CONFIG.FALLBACK.Icon;
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import { MODEL_FAMILY_CONFIG } from '~/pages/settings/ai/constants/SettingsAiModelProviders';
|
||||
|
||||
export const getModelProviderLabel = (
|
||||
modelFamily: string | null | undefined,
|
||||
): string => {
|
||||
if (!modelFamily) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const key = modelFamily.toLowerCase();
|
||||
|
||||
return MODEL_FAMILY_CONFIG[key]?.label ?? modelFamily;
|
||||
};
|
||||
@@ -87,10 +87,6 @@ export const mockCurrentWorkspace = {
|
||||
fastModel: DEFAULT_FAST_MODEL,
|
||||
smartModel: DEFAULT_SMART_MODEL,
|
||||
routerModel: 'auto',
|
||||
autoEnableNewAiModels: true,
|
||||
disabledAiModelIds: [],
|
||||
enabledAiModelIds: [],
|
||||
useRecommendedModels: true,
|
||||
currentBillingSubscription: {
|
||||
__typename: 'BillingSubscription',
|
||||
id: '7efbc3f7-6e5e-4128-957e-8d86808cdf6a',
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAiModelAvailabilityColumns1771768847449
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddAiModelAvailabilityColumns1771768847449';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "autoEnableNewAiModels" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "disabledAiModelIds" character varying array NOT NULL DEFAULT '{}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "enabledAiModelIds" character varying array NOT NULL DEFAULT '{}'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "enabledAiModelIds"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "disabledAiModelIds"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "autoEnableNewAiModels"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddUseRecommendedModels1771840510112
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddUseRecommendedModels1771840510112';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "useRecommendedModels" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "useRecommendedModels"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
|
||||
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
|
||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||
import { AdminAIModelsOutput } from 'src/engine/core-modules/client-config/client-config.entity';
|
||||
import { ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||
import { ConfigVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/config-variables.output';
|
||||
import { DeleteJobsResponseDTO } from 'src/engine/core-modules/admin-panel/dtos/delete-jobs-response.dto';
|
||||
@@ -31,7 +30,6 @@ import { type MessageQueue } from 'src/engine/core-modules/message-queue/message
|
||||
import { type ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables';
|
||||
import { ConfigVariableGraphqlApiExceptionFilter } from 'src/engine/core-modules/twenty-config/filters/config-variable-graphql-api-exception.filter';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { AdminPanelGuard } from 'src/engine/guards/admin-panel-guard';
|
||||
import { ServerLevelImpersonateGuard } from 'src/engine/guards/server-level-impersonate.guard';
|
||||
@@ -61,7 +59,6 @@ export class AdminPanelResolver {
|
||||
private adminPanelQueueService: AdminPanelQueueService,
|
||||
private featureFlagService: FeatureFlagService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
) {}
|
||||
|
||||
@UseGuards(ServerLevelImpersonateGuard)
|
||||
@@ -141,41 +138,6 @@ export class AdminPanelResolver {
|
||||
return this.adminService.getVersionInfo();
|
||||
}
|
||||
|
||||
@UseGuards(AdminPanelGuard)
|
||||
@Query(() => AdminAIModelsOutput)
|
||||
async getAdminAiModels(): Promise<AdminAIModelsOutput> {
|
||||
const models = this.aiModelRegistryService
|
||||
.getAllModelsWithStatus()
|
||||
.map(({ modelConfig, isAvailable, isAdminEnabled }) => ({
|
||||
modelId: modelConfig.modelId,
|
||||
label: modelConfig.label,
|
||||
modelFamily: modelConfig.modelFamily,
|
||||
inferenceProvider: modelConfig.inferenceProvider,
|
||||
isAvailable,
|
||||
isAdminEnabled,
|
||||
deprecated: modelConfig.deprecated,
|
||||
isRecommended: modelConfig.isRecommended,
|
||||
}));
|
||||
|
||||
return {
|
||||
autoEnableNewModels: this.twentyConfigService.get(
|
||||
'AI_AUTO_ENABLE_NEW_MODELS',
|
||||
),
|
||||
models,
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(AdminPanelGuard)
|
||||
@Mutation(() => Boolean)
|
||||
async setAdminAiModelEnabled(
|
||||
@Args('modelId', { type: () => String }) modelId: string,
|
||||
@Args('enabled', { type: () => Boolean }) enabled: boolean,
|
||||
): Promise<boolean> {
|
||||
await this.aiModelRegistryService.setModelAdminEnabled(modelId, enabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@UseGuards(AdminPanelGuard)
|
||||
@Query(() => ConfigVariableDTO)
|
||||
async getDatabaseConfigVariable(
|
||||
|
||||
@@ -58,45 +58,6 @@ export class ClientAIModelConfig {
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
deprecated?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
isRecommended?: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class AdminAIModelConfig {
|
||||
@Field(() => String)
|
||||
modelId: string;
|
||||
|
||||
@Field(() => String)
|
||||
label: string;
|
||||
|
||||
@Field(() => ModelFamily, { nullable: true })
|
||||
modelFamily?: ModelFamily;
|
||||
|
||||
@Field(() => InferenceProvider)
|
||||
inferenceProvider: InferenceProvider;
|
||||
|
||||
@Field(() => Boolean)
|
||||
isAvailable: boolean;
|
||||
|
||||
@Field(() => Boolean)
|
||||
isAdminEnabled: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
deprecated?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
isRecommended?: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class AdminAIModelsOutput {
|
||||
@Field(() => Boolean)
|
||||
autoEnableNewModels: boolean;
|
||||
|
||||
@Field(() => [AdminAIModelConfig])
|
||||
models: AdminAIModelConfig[];
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
||||
+3
-5
@@ -43,8 +43,7 @@ export class ClientConfigService {
|
||||
'CALENDAR_BOOKING_PAGE_ID',
|
||||
);
|
||||
|
||||
const availableModels =
|
||||
this.aiModelRegistryService.getAdminFilteredModels();
|
||||
const availableModels = this.aiModelRegistryService.getAvailableModels();
|
||||
|
||||
const aiModels: ClientAIModelConfig[] = availableModels.map(
|
||||
(registeredModel) => {
|
||||
@@ -69,7 +68,6 @@ export class ClientConfigService {
|
||||
)
|
||||
: 0,
|
||||
deprecated: builtInModel?.deprecated,
|
||||
isRecommended: builtInModel?.isRecommended,
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -98,14 +96,14 @@ export class ClientConfigService {
|
||||
aiModels.unshift(
|
||||
{
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
label: `Best (${defaultPerformanceModelLabel})`,
|
||||
label: `Smart (${defaultPerformanceModelLabel})`,
|
||||
inferenceProvider: InferenceProvider.NONE,
|
||||
inputCostPerMillionTokensInCredits: 0,
|
||||
outputCostPerMillionTokensInCredits: 0,
|
||||
},
|
||||
{
|
||||
modelId: DEFAULT_FAST_MODEL,
|
||||
label: `Best (${defaultSpeedModelLabel})`,
|
||||
label: `Fast (${defaultSpeedModelLabel})`,
|
||||
inferenceProvider: InferenceProvider.NONE,
|
||||
inputCostPerMillionTokensInCredits: 0,
|
||||
outputCostPerMillionTokensInCredits: 0,
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Sondag"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "الأحد"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Diumenge"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Neděle"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Søndag"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Sonntag"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Κυριακή"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3669,12 +3669,12 @@ msgid "Sunday"
|
||||
msgstr "Sunday"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr "System field {expectedFieldName} has invalid {property}"
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr "System field {expectedFieldName} is missing"
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Domingo"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Sunnuntai"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Dimanche"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "יום ראשון"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Vasárnap"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Domenica"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "日曜日"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "일요일"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Zondag"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Søndag"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Niedziela"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3669,12 +3669,12 @@ msgid "Sunday"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Domingo"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Domingo"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Duminică"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Воскресенье"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Недеља"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3680,12 +3680,12 @@ msgstr ""
|
||||
"nd"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Pazar"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Неділя"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "Chủ Nhật"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "星期日"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3674,12 +3674,12 @@ msgid "Sunday"
|
||||
msgstr "星期日"
|
||||
|
||||
#. js-lingui-id: Bhmfez
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} has invalid {property}"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 1gOTOh
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util.ts
|
||||
#: src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.ts
|
||||
msgid "System field {expectedFieldName} is missing"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -1326,33 +1326,6 @@ export class ConfigVariables {
|
||||
@IsOptional()
|
||||
AWS_BEDROCK_SESSION_TOKEN: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
description:
|
||||
'When true, newly added models are automatically available to all workspaces',
|
||||
type: ConfigVariableType.BOOLEAN,
|
||||
})
|
||||
@IsOptional()
|
||||
AI_AUTO_ENABLE_NEW_MODELS = true;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
description:
|
||||
'Model IDs to disable (used when AI_AUTO_ENABLE_NEW_MODELS is true)',
|
||||
type: ConfigVariableType.ARRAY,
|
||||
})
|
||||
@IsOptional()
|
||||
AI_DISABLED_MODEL_IDS: string[] = [];
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.LLM,
|
||||
description:
|
||||
'Model IDs to enable (used when AI_AUTO_ENABLE_NEW_MODELS is false)',
|
||||
type: ConfigVariableType.ARRAY,
|
||||
})
|
||||
@IsOptional()
|
||||
AI_ENABLED_MODEL_IDS: string[] = [];
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVER_CONFIG,
|
||||
description: 'Enable or disable multi-workspace support',
|
||||
|
||||
-22
@@ -127,26 +127,4 @@ export class UpdateWorkspaceInput {
|
||||
@IsString({ each: true })
|
||||
@IsOptional()
|
||||
editableProfileFields?: string[];
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
autoEnableNewAiModels?: boolean;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsOptional()
|
||||
disabledAiModelIds?: string[];
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@IsOptional()
|
||||
enabledAiModelIds?: string[];
|
||||
|
||||
@Field({ nullable: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
useRecommendedModels?: boolean;
|
||||
}
|
||||
|
||||
-49
@@ -37,8 +37,6 @@ import {
|
||||
WorkspaceExceptionCode,
|
||||
WorkspaceNotFoundDefaultError,
|
||||
} from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { isModelAllowedByWorkspace } from 'src/engine/metadata-modules/ai/ai-models/utils/is-model-allowed.util';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ALL_METADATA_ENTITY_BY_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant';
|
||||
import { ALL_METADATA_NAMES_SORTED_ATOMICALLY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-names-sorted-atomically.constant';
|
||||
@@ -88,10 +86,6 @@ export class WorkspaceService extends TypeOrmQueryService<WorkspaceEntity> {
|
||||
fastModel: PermissionFlagType.WORKSPACE,
|
||||
smartModel: PermissionFlagType.WORKSPACE,
|
||||
aiAdditionalInstructions: PermissionFlagType.WORKSPACE,
|
||||
autoEnableNewAiModels: PermissionFlagType.AI_SETTINGS,
|
||||
disabledAiModelIds: PermissionFlagType.AI_SETTINGS,
|
||||
enabledAiModelIds: PermissionFlagType.AI_SETTINGS,
|
||||
useRecommendedModels: PermissionFlagType.AI_SETTINGS,
|
||||
};
|
||||
|
||||
constructor(
|
||||
@@ -116,7 +110,6 @@ export class WorkspaceService extends TypeOrmQueryService<WorkspaceEntity> {
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
private readonly customDomainManagerService: CustomDomainManagerService,
|
||||
private readonly fileCorePictureService: FileCorePictureService,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
@InjectMessageQueue(MessageQueue.deleteCascadeQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
@InjectDataSource()
|
||||
@@ -223,48 +216,6 @@ export class WorkspaceService extends TypeOrmQueryService<WorkspaceEntity> {
|
||||
);
|
||||
}
|
||||
|
||||
const isChangingModels =
|
||||
isDefined(payload.smartModel) || isDefined(payload.fastModel);
|
||||
const isChangingAvailability =
|
||||
payload.useRecommendedModels !== undefined ||
|
||||
payload.autoEnableNewAiModels !== undefined ||
|
||||
payload.disabledAiModelIds !== undefined ||
|
||||
payload.enabledAiModelIds !== undefined;
|
||||
|
||||
if (isChangingModels || isChangingAvailability) {
|
||||
const effectiveWorkspace = {
|
||||
useRecommendedModels:
|
||||
payload.useRecommendedModels ?? workspace.useRecommendedModels,
|
||||
autoEnableNewAiModels:
|
||||
payload.autoEnableNewAiModels ?? workspace.autoEnableNewAiModels,
|
||||
disabledAiModelIds:
|
||||
payload.disabledAiModelIds ?? workspace.disabledAiModelIds,
|
||||
enabledAiModelIds:
|
||||
payload.enabledAiModelIds ?? workspace.enabledAiModelIds,
|
||||
};
|
||||
|
||||
const modelsToValidate = [
|
||||
payload.smartModel ?? workspace.smartModel,
|
||||
payload.fastModel ?? workspace.fastModel,
|
||||
].filter(isDefined);
|
||||
|
||||
for (const modelId of modelsToValidate) {
|
||||
if (!this.aiModelRegistryService.isModelAdminAllowed(modelId)) {
|
||||
throw new WorkspaceException(
|
||||
'Selected model has been disabled by the administrator',
|
||||
WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isModelAllowedByWorkspace(modelId, effectiveWorkspace)) {
|
||||
throw new WorkspaceException(
|
||||
'Selected model is not available in this workspace',
|
||||
WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let updatedWorkspace: WorkspaceEntity;
|
||||
|
||||
try {
|
||||
|
||||
@@ -314,32 +314,6 @@ export class WorkspaceEntity {
|
||||
@Column({ type: 'text', nullable: true })
|
||||
aiAdditionalInstructions: string | null;
|
||||
|
||||
@Field(() => Boolean, { nullable: false })
|
||||
@Column({ type: 'boolean', nullable: false, default: true })
|
||||
autoEnableNewAiModels: boolean;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
array: true,
|
||||
nullable: false,
|
||||
default: '{}',
|
||||
})
|
||||
disabledAiModelIds: string[];
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
array: true,
|
||||
nullable: false,
|
||||
default: '{}',
|
||||
})
|
||||
enabledAiModelIds: string[];
|
||||
|
||||
@Field(() => Boolean, { nullable: false })
|
||||
@Column({ type: 'boolean', nullable: false, default: true })
|
||||
useRecommendedModels: boolean;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceCustomApplicationId: string;
|
||||
|
||||
|
||||
@@ -260,34 +260,6 @@ export class WorkspaceResolver {
|
||||
return workspace.smartModel;
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean, { nullable: false })
|
||||
async autoEnableNewAiModels(
|
||||
@Parent() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
return workspace.autoEnableNewAiModels;
|
||||
}
|
||||
|
||||
@ResolveField(() => [String], { nullable: true })
|
||||
async disabledAiModelIds(
|
||||
@Parent() workspace: WorkspaceEntity,
|
||||
): Promise<string[]> {
|
||||
return workspace.disabledAiModelIds;
|
||||
}
|
||||
|
||||
@ResolveField(() => [String], { nullable: true })
|
||||
async enabledAiModelIds(
|
||||
@Parent() workspace: WorkspaceEntity,
|
||||
): Promise<string[]> {
|
||||
return workspace.enabledAiModelIds;
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean, { nullable: false })
|
||||
async useRecommendedModels(
|
||||
@Parent() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
return workspace.useRecommendedModels;
|
||||
}
|
||||
|
||||
@ResolveField(() => ApplicationDTO, { nullable: true })
|
||||
async workspaceCustomApplication(
|
||||
@Parent() workspace: WorkspaceEntity,
|
||||
|
||||
-2
@@ -8,7 +8,6 @@ import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
@@ -37,7 +36,6 @@ import { AgentAsyncExecutorService } from './services/agent-async-executor.servi
|
||||
AgentMessagePartEntity,
|
||||
AgentTurnEntity,
|
||||
RoleTargetEntity,
|
||||
WorkspaceEntity,
|
||||
]),
|
||||
],
|
||||
providers: [AgentAsyncExecutorService, AgentActorContextService],
|
||||
|
||||
-16
@@ -30,7 +30,6 @@ import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entiti
|
||||
import { repairToolCall } from 'src/engine/metadata-modules/ai/ai-agent/utils/repair-tool-call.util';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/agent-model-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
@@ -48,8 +47,6 @@ export class AgentAsyncExecutorService {
|
||||
private readonly toolRegistry: ToolRegistryService,
|
||||
@InjectRepository(RoleTargetEntity)
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
private extractRoleIds(
|
||||
@@ -111,19 +108,6 @@ export class AgentAsyncExecutorService {
|
||||
authContext?: WorkspaceAuthContext;
|
||||
}): Promise<AgentExecutionResult> {
|
||||
try {
|
||||
if (agent) {
|
||||
const workspace = await this.workspaceRepository.findOneBy({
|
||||
id: agent.workspaceId,
|
||||
});
|
||||
|
||||
if (workspace) {
|
||||
this.aiModelRegistryService.validateModelAvailability(
|
||||
agent.modelId,
|
||||
workspace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const registeredModel =
|
||||
await this.aiModelRegistryService.resolveModelForAgent(agent);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import { Args, Mutation, Query } from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -16,7 +15,6 @@ import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.g
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
|
||||
import { AgentService } from './agent.service';
|
||||
|
||||
@@ -37,10 +35,7 @@ import { AgentGraphqlApiExceptionInterceptor } from './interceptors/agent-graphq
|
||||
)
|
||||
@MetadataResolver()
|
||||
export class AgentResolver {
|
||||
constructor(
|
||||
private readonly agentService: AgentService,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
) {}
|
||||
constructor(private readonly agentService: AgentService) {}
|
||||
|
||||
@Query(() => [AgentDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
@@ -72,18 +67,11 @@ export class AgentResolver {
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.AI_SETTINGS))
|
||||
async createOneAgent(
|
||||
@Args('input') input: CreateAgentInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<AgentDTO> {
|
||||
if (isDefined(input.modelId)) {
|
||||
this.aiModelRegistryService.validateModelAvailability(
|
||||
input.modelId,
|
||||
workspace,
|
||||
);
|
||||
}
|
||||
|
||||
const createdAgent = await this.agentService.createOneAgent(
|
||||
{ ...input, isCustom: true },
|
||||
workspace.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return fromFlatAgentWithRoleIdToAgentDto(createdAgent);
|
||||
@@ -94,18 +82,11 @@ export class AgentResolver {
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.AI_SETTINGS))
|
||||
async updateOneAgent(
|
||||
@Args('input') input: UpdateAgentInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<AgentDTO> {
|
||||
if (isDefined(input.modelId)) {
|
||||
this.aiModelRegistryService.validateModelAvailability(
|
||||
input.modelId,
|
||||
workspace,
|
||||
);
|
||||
}
|
||||
|
||||
const updatedAgent = await this.agentService.updateOneAgent({
|
||||
input,
|
||||
workspaceId: workspace.id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatAgentWithRoleIdToAgentDto(updatedAgent);
|
||||
|
||||
+3
-8
@@ -64,20 +64,15 @@ export class AgentChatController {
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Res() response: Response,
|
||||
) {
|
||||
if (this.aiModelRegistryService.getAvailableModels().length === 0) {
|
||||
const availableModels = this.aiModelRegistryService.getAvailableModels();
|
||||
|
||||
if (availableModels.length === 0) {
|
||||
throw new AgentException(
|
||||
'No AI models are available. Please configure at least one AI provider API key (OPENAI_API_KEY, ANTHROPIC_API_KEY, or XAI_API_KEY).',
|
||||
AgentExceptionCode.API_KEY_NOT_CONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
const resolvedModelId = workspace.smartModel;
|
||||
|
||||
this.aiModelRegistryService.validateModelAvailability(
|
||||
resolvedModelId,
|
||||
workspace,
|
||||
);
|
||||
|
||||
if (this.twentyConfigService.get('IS_BILLING_ENABLED')) {
|
||||
const canBill = await this.billingService.canBillMeteredProduct(
|
||||
workspace.id,
|
||||
|
||||
+2
-5
@@ -126,13 +126,10 @@ export class ChatExecutionService {
|
||||
|
||||
const preloadedToolNames = Object.keys(preloadedTools);
|
||||
|
||||
const modelId = workspace.smartModel;
|
||||
|
||||
this.aiModelRegistryService.validateModelAvailability(modelId, workspace);
|
||||
|
||||
// Respect the workspace's model preference (Settings > AI > Model Router)
|
||||
const registeredModel =
|
||||
await this.aiModelRegistryService.resolveModelForAgent({
|
||||
modelId,
|
||||
modelId: workspace.smartModel,
|
||||
});
|
||||
|
||||
const modelConfig = this.aiModelRegistryService.getEffectiveModelConfig(
|
||||
|
||||
-1
@@ -101,5 +101,4 @@ export interface AIModelConfig {
|
||||
twitterSearch?: boolean;
|
||||
};
|
||||
deprecated?: boolean;
|
||||
isRecommended?: boolean;
|
||||
}
|
||||
|
||||
-2
@@ -40,7 +40,6 @@ export const ANTHROPIC_MODELS: AIModelConfig[] = [
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
isRecommended: true,
|
||||
},
|
||||
{
|
||||
modelId: 'claude-sonnet-4-6',
|
||||
@@ -76,7 +75,6 @@ export const ANTHROPIC_MODELS: AIModelConfig[] = [
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
isRecommended: true,
|
||||
},
|
||||
{
|
||||
modelId: 'claude-sonnet-4-5-20250929',
|
||||
|
||||
-1
@@ -26,7 +26,6 @@ export const GOOGLE_MODELS: AIModelConfig[] = [
|
||||
maxOutputTokens: 65536,
|
||||
supportedFileTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
|
||||
doesSupportThinking: true,
|
||||
isRecommended: true,
|
||||
},
|
||||
{
|
||||
modelId: 'gemini-3-flash-preview',
|
||||
|
||||
-2
@@ -23,7 +23,6 @@ export const OPENAI_MODELS: AIModelConfig[] = [
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
isRecommended: true,
|
||||
},
|
||||
{
|
||||
modelId: 'gpt-5-mini',
|
||||
@@ -56,7 +55,6 @@ export const OPENAI_MODELS: AIModelConfig[] = [
|
||||
nativeCapabilities: {
|
||||
webSearch: true,
|
||||
},
|
||||
isRecommended: true,
|
||||
},
|
||||
{
|
||||
modelId: 'gpt-4.1-mini',
|
||||
|
||||
-1
@@ -23,7 +23,6 @@ export const XAI_MODELS: AIModelConfig[] = [
|
||||
webSearch: true,
|
||||
twitterSearch: true,
|
||||
},
|
||||
isRecommended: true,
|
||||
},
|
||||
{
|
||||
modelId: 'grok-4-1-fast-reasoning',
|
||||
|
||||
-95
@@ -32,10 +32,6 @@ import { GROQ_MODELS } from 'src/engine/metadata-modules/ai/ai-models/constants/
|
||||
import { MISTRAL_MODELS } from 'src/engine/metadata-modules/ai/ai-models/constants/mistral-models.const';
|
||||
import { OPENAI_MODELS } from 'src/engine/metadata-modules/ai/ai-models/constants/openai-models.const';
|
||||
import { XAI_MODELS } from 'src/engine/metadata-modules/ai/ai-models/constants/xai-models.const';
|
||||
import {
|
||||
isModelAllowedByWorkspace,
|
||||
type WorkspaceModelAvailabilitySettings,
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/utils/is-model-allowed.util';
|
||||
|
||||
export interface RegisteredAIModel {
|
||||
modelId: string;
|
||||
@@ -375,97 +371,6 @@ export class AiModelRegistryService {
|
||||
return providerToFamily[inferenceProvider] ?? ModelFamily.OPENAI;
|
||||
}
|
||||
|
||||
isModelAdminAllowed(modelId: string): boolean {
|
||||
if (modelId === DEFAULT_FAST_MODEL || modelId === DEFAULT_SMART_MODEL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const autoEnable = this.twentyConfigService.get(
|
||||
'AI_AUTO_ENABLE_NEW_MODELS',
|
||||
);
|
||||
const disabledIds = this.twentyConfigService.get('AI_DISABLED_MODEL_IDS');
|
||||
const enabledIds = this.twentyConfigService.get('AI_ENABLED_MODEL_IDS');
|
||||
|
||||
return autoEnable
|
||||
? !disabledIds.includes(modelId)
|
||||
: enabledIds.includes(modelId);
|
||||
}
|
||||
|
||||
validateModelAvailability(
|
||||
modelId: string,
|
||||
workspace: WorkspaceModelAvailabilitySettings,
|
||||
): void {
|
||||
if (!this.isModelAdminAllowed(modelId)) {
|
||||
throw new AgentException(
|
||||
'The selected model has been disabled by the administrator.',
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isModelAllowedByWorkspace(modelId, workspace)) {
|
||||
throw new AgentException(
|
||||
'The selected model is not available in this workspace.',
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getAdminFilteredModels(): RegisteredAIModel[] {
|
||||
return this.getAvailableModels().filter((model) =>
|
||||
this.isModelAdminAllowed(model.modelId),
|
||||
);
|
||||
}
|
||||
|
||||
getAllModelsWithStatus(): Array<{
|
||||
modelConfig: AIModelConfig;
|
||||
isAvailable: boolean;
|
||||
isAdminEnabled: boolean;
|
||||
}> {
|
||||
return AI_MODELS.map((model) => ({
|
||||
modelConfig: model,
|
||||
isAvailable: this.modelRegistry.has(model.modelId),
|
||||
isAdminEnabled: this.isModelAdminAllowed(model.modelId),
|
||||
}));
|
||||
}
|
||||
|
||||
async setModelAdminEnabled(modelId: string, enabled: boolean): Promise<void> {
|
||||
const isKnownModel = AI_MODELS.some((model) => model.modelId === modelId);
|
||||
|
||||
if (!isKnownModel) {
|
||||
throw new AgentException(
|
||||
`Unknown model ID: ${modelId}`,
|
||||
AgentExceptionCode.AGENT_EXECUTION_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
const autoEnable = this.twentyConfigService.get(
|
||||
'AI_AUTO_ENABLE_NEW_MODELS',
|
||||
);
|
||||
const disabledIds = this.twentyConfigService.get('AI_DISABLED_MODEL_IDS');
|
||||
const enabledIds = this.twentyConfigService.get('AI_ENABLED_MODEL_IDS');
|
||||
|
||||
if (autoEnable) {
|
||||
const newDisabledIds = enabled
|
||||
? disabledIds.filter((id) => id !== modelId)
|
||||
: disabledIds.includes(modelId)
|
||||
? disabledIds
|
||||
: [...disabledIds, modelId];
|
||||
|
||||
await this.twentyConfigService.set(
|
||||
'AI_DISABLED_MODEL_IDS',
|
||||
newDisabledIds,
|
||||
);
|
||||
} else {
|
||||
const newEnabledIds = enabled
|
||||
? enabledIds.includes(modelId)
|
||||
? enabledIds
|
||||
: [...enabledIds, modelId]
|
||||
: enabledIds.filter((id) => id !== modelId);
|
||||
|
||||
await this.twentyConfigService.set('AI_ENABLED_MODEL_IDS', newEnabledIds);
|
||||
}
|
||||
}
|
||||
|
||||
refreshRegistry(): void {
|
||||
this.buildModelRegistry();
|
||||
}
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
import {
|
||||
AI_MODELS,
|
||||
DEFAULT_FAST_MODEL,
|
||||
DEFAULT_SMART_MODEL,
|
||||
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
export type WorkspaceModelAvailabilitySettings = {
|
||||
useRecommendedModels: boolean;
|
||||
autoEnableNewAiModels: boolean;
|
||||
disabledAiModelIds: string[];
|
||||
enabledAiModelIds: string[];
|
||||
};
|
||||
|
||||
const RECOMMENDED_MODEL_IDS = new Set(
|
||||
AI_MODELS.filter((model) => model.isRecommended).map(
|
||||
(model) => model.modelId,
|
||||
),
|
||||
);
|
||||
|
||||
export const isModelAllowedByWorkspace = (
|
||||
modelId: string,
|
||||
workspace: WorkspaceModelAvailabilitySettings,
|
||||
): boolean => {
|
||||
if (modelId === DEFAULT_FAST_MODEL || modelId === DEFAULT_SMART_MODEL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (workspace.useRecommendedModels) {
|
||||
return RECOMMENDED_MODEL_IDS.has(modelId);
|
||||
}
|
||||
|
||||
return workspace.autoEnableNewAiModels
|
||||
? !workspace.disabledAiModelIds.includes(modelId)
|
||||
: workspace.enabledAiModelIds.includes(modelId);
|
||||
};
|
||||
+36
-13
@@ -107,22 +107,45 @@ export const buildRowLevelPermissionRecordFilter = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const rawWorkspaceMemberValue = Object.entries(workspaceMember).find(
|
||||
([key]) => key === workspaceMemberFieldMetadata.name,
|
||||
)?.[1];
|
||||
if (workspaceMemberFieldMetadata.type === FieldMetadataType.RELATION) {
|
||||
const joinColumnName = (
|
||||
workspaceMemberFieldMetadata.settings as {
|
||||
joinColumnName?: string;
|
||||
} | null
|
||||
)?.joinColumnName;
|
||||
|
||||
const workspaceMemberSubFieldName =
|
||||
predicate.workspaceMemberSubFieldName;
|
||||
if (!isDefined(joinColumnName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(workspaceMemberSubFieldName) &&
|
||||
isDefined(rawWorkspaceMemberValue) &&
|
||||
isCompositeFieldMetadataType(workspaceMemberFieldMetadata.type) &&
|
||||
typeof rawWorkspaceMemberValue === 'object'
|
||||
) {
|
||||
predicateValue = rawWorkspaceMemberValue[workspaceMemberSubFieldName];
|
||||
const fkValue = Object.entries(workspaceMember).find(
|
||||
([key]) => key === joinColumnName,
|
||||
)?.[1];
|
||||
|
||||
if (!isDefined(fkValue) || typeof fkValue !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
predicateValue = { selectedRecordIds: [fkValue] };
|
||||
} else {
|
||||
predicateValue = rawWorkspaceMemberValue;
|
||||
const rawWorkspaceMemberValue = Object.entries(workspaceMember).find(
|
||||
([key]) => key === workspaceMemberFieldMetadata.name,
|
||||
)?.[1];
|
||||
|
||||
const workspaceMemberSubFieldName =
|
||||
predicate.workspaceMemberSubFieldName;
|
||||
|
||||
if (
|
||||
isDefined(workspaceMemberSubFieldName) &&
|
||||
isDefined(rawWorkspaceMemberValue) &&
|
||||
isCompositeFieldMetadataType(workspaceMemberFieldMetadata.type) &&
|
||||
typeof rawWorkspaceMemberValue === 'object'
|
||||
) {
|
||||
predicateValue =
|
||||
rawWorkspaceMemberValue[workspaceMemberSubFieldName];
|
||||
} else {
|
||||
predicateValue = rawWorkspaceMemberValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDefined(predicateValue)) {
|
||||
|
||||
Reference in New Issue
Block a user