@nextclaw/ui 0.14.1-beta.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/dist/assets/{api-C6EDHBDR.js → api-DPcvCLfa.js} +3 -3
- package/dist/assets/{channels-list-page-DKj6m1Hc.js → channels-list-page-D6jtziu4.js} +2 -2
- package/dist/assets/chat-page-BBnNJoKG.js +105 -0
- package/dist/assets/{config-split-page-CjlmJy8X.js → config-split-page-BCJhqdK9.js} +1 -1
- package/dist/assets/{confirm-dialog-B39X0pYp.js → confirm-dialog-CWh5FfK2.js} +1 -1
- package/dist/assets/{desktop-update-config-DfUQ8nly.js → desktop-update-config-DsRhBizi.js} +1 -1
- package/dist/assets/{dist-CmMro3nV.js → dist-DPrgcEC0.js} +1 -1
- package/dist/assets/{dist-BIYV2-Ia.js → dist-DsE4OsSS.js} +1 -1
- package/dist/assets/doc-browser-C8xOF_9F.js +1 -0
- package/dist/assets/{doc-browser-CDtUtcim.js → doc-browser-DgQedeAn.js} +1 -1
- package/dist/assets/{doc-browser-context-DZbC9zVB.js → doc-browser-context-B46tQGmO.js} +1 -1
- package/dist/assets/{ellipsis-DDpgTzgx.js → ellipsis-B81FStLm.js} +1 -1
- package/dist/assets/{external-link-D2cBOCqn.js → external-link-C_dmG-WJ.js} +1 -1
- package/dist/assets/index-7o1WdUbU.js +103 -0
- package/dist/assets/index-DQWmEWzk.css +1 -0
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +1 -0
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +40 -0
- package/dist/assets/model-config-page-Cv0O3BIj.js +1 -0
- package/dist/assets/plus-Bnt56oqG.js +1 -0
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +1 -0
- package/dist/assets/providers-config-page-CPDz0Lxc.js +1 -0
- package/dist/assets/{react-DYQXXGbH.js → react-sZz5HSCm.js} +1 -1
- package/dist/assets/remote-B0LMs8u7.js +1 -0
- package/dist/assets/rotate-cw-BfyG_KEF.js +1 -0
- package/dist/assets/runtime-config-page-q0fvv8ME.js +1 -0
- package/dist/assets/{save-BNVTgBVn.js → save-MzjoiHri.js} +1 -1
- package/dist/assets/{search-CKCzwLId.js → search-B29dE4GN.js} +1 -1
- package/dist/assets/{search-config-page-DG5Q2hF1.js → search-config-page-Dl5sZzob.js} +1 -1
- package/dist/assets/secrets-config-page-C_l-IFCK.js +3 -0
- package/dist/assets/{select-CIRwTNP4.js → select-BKmh05hd.js} +2 -2
- package/dist/assets/{tag-chip-DKhVFtTE.js → tag-chip-BS_7Ueom.js} +1 -1
- package/dist/assets/use-config-DZkBWIdL.js +1 -0
- package/dist/assets/x-CW2KO-Hz.js +1 -0
- package/dist/index.html +14 -14
- package/package.json +9 -9
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +12 -63
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +11 -50
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +15 -2
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +70 -7
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +104 -16
- package/src/features/chat/components/layout/chat-sidebar.tsx +21 -2
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +84 -6
- package/src/features/chat/features/input/hooks/use-chat-input-bar-query-state.ts +2 -0
- package/src/features/chat/features/input/hooks/use-chat-model-favorites.ts +88 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +64 -24
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +1 -0
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +21 -12
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +80 -25
- package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +310 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-test.utils.ts +43 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +1 -346
- package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +10 -9
- package/src/features/chat/features/message/utils/file-operation/diff.utils.ts +39 -36
- package/src/features/chat/features/message/utils/file-operation/line-builder.utils.ts +46 -38
- package/src/features/chat/features/message/utils/file-operation/parsed-block.utils.ts +18 -15
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +3 -2
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +25 -25
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +15 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +24 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +27 -1
- package/src/features/chat/features/session-type/components/{chat-sidebar-create-menu.tsx → chat-session-type-menu.tsx} +17 -4
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +15 -3
- package/src/features/chat/features/session-type/hooks/__tests__/use-chat-new-session-type-preference.test.tsx +152 -0
- package/src/features/chat/features/session-type/hooks/use-chat-new-session-type-preference.ts +159 -0
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +193 -0
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +123 -0
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +104 -0
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +72 -0
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +54 -0
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +132 -0
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +82 -0
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +121 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +53 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-selection.utils.test.ts +63 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-visibility.utils.test.ts +34 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +71 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-selection.utils.ts +79 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-visibility.utils.ts +17 -0
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +65 -0
- package/src/features/chat/managers/chat-input.manager.ts +24 -2
- package/src/features/chat/stores/chat-input.store.ts +2 -0
- package/src/features/chat/types/chat-input-bar.types.ts +5 -0
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +24 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +8 -3
- package/src/features/marketplace/components/marketplace-item-list-view.tsx +2 -2
- package/src/features/marketplace/components/marketplace-list-card.tsx +124 -98
- package/src/features/marketplace/components/marketplace-page-parts.tsx +11 -6
- package/src/features/marketplace/utils/marketplace-installed-cache.utils.ts +27 -23
- package/src/features/settings/components/config/provider-form.tsx +284 -387
- package/src/features/settings/components/config/provider-status-badge.tsx +4 -4
- package/src/features/settings/components/config/secrets-config-form.tsx +11 -135
- package/src/features/settings/hooks/use-provider-auth-flow.ts +140 -0
- package/src/features/settings/pages/__tests__/model-config-page.test.tsx +16 -0
- package/src/features/settings/pages/secrets-config-page.tsx +2 -1
- package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +60 -1
- package/src/features/settings/utils/__tests__/secrets-config-form.utils.test.ts +66 -0
- package/src/features/settings/utils/provider-form-context.utils.ts +131 -0
- package/src/features/settings/utils/provider-form-model.utils.ts +101 -0
- package/src/features/settings/utils/provider-form-support.utils.ts +102 -1
- package/src/features/settings/utils/secrets-config-form.utils.ts +139 -0
- package/src/features/system-status/components/config/runtime-agent-list-card.tsx +17 -11
- package/src/features/system-status/components/config/runtime-binding-list-card.tsx +14 -9
- package/src/features/system-status/components/config/runtime-config-editor.tsx +8 -5
- package/src/features/system-status/components/config/runtime-entry-list-card.tsx +14 -9
- package/src/features/system-status/components/config/runtime-settings-card.tsx +16 -7
- package/src/features/system-status/components/runtime-security-card.tsx +79 -25
- package/src/shared/components/common/__tests__/agent-avatar.test.tsx +3 -1
- package/src/shared/components/common/__tests__/searchable-model-input.test.tsx +34 -0
- package/src/shared/components/common/agent-avatar.tsx +5 -2
- package/src/shared/components/common/provider-scoped-model-input.tsx +1 -0
- package/src/shared/components/common/searchable-model-input.tsx +102 -87
- package/src/shared/components/ui/README.md +7 -0
- package/src/shared/components/ui/popover.tsx +19 -3
- package/src/shared/components/ui/select.tsx +14 -4
- package/src/shared/hooks/use-infinite-scroll-loader.ts +14 -10
- package/src/shared/lib/api/index.ts +3 -0
- package/src/shared/lib/api/preferences/preference-keys.config.ts +9 -0
- package/src/shared/lib/api/preferences/preference.types.ts +22 -0
- package/src/shared/lib/api/preferences/preference.utils.ts +40 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +16 -1
- package/src/shared/lib/i18n/locales/en-US/core.json +1 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -1
- package/src/shared/lib/i18n/locales/zh-CN/core.json +1 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +2 -0
- package/dist/assets/chat-page-BXngLJY6.js +0 -105
- package/dist/assets/doc-browser-B6Wc_TIY.js +0 -1
- package/dist/assets/index-DTb_XVIH.js +0 -103
- package/dist/assets/index-ayiPYhf1.css +0 -1
- package/dist/assets/mcp-marketplace-page-DZ9H0v05.js +0 -1
- package/dist/assets/mcp-marketplace-page-cnVWf7QQ.js +0 -40
- package/dist/assets/model-config-page-Bcb35AVv.js +0 -1
- package/dist/assets/plus-CYE1onU2.js +0 -1
- package/dist/assets/provider-scoped-model-input-kdoNp4HG.js +0 -1
- package/dist/assets/providers-config-page-CQSibSwO.js +0 -1
- package/dist/assets/remote-BlJDbXOI.js +0 -1
- package/dist/assets/rotate-cw-BWbvFDAX.js +0 -1
- package/dist/assets/runtime-config-page-Tn2yLQ0c.js +0 -1
- package/dist/assets/secrets-config-page-BXn5Uuwp.js +0 -3
- package/dist/assets/use-config-DgKM9VIU.js +0 -1
- package/dist/assets/x-Cm3JjYut.js +0 -1
- package/src/features/chat/components/__tests__/chat-welcome.test.tsx +0 -45
- package/src/features/chat/components/chat-welcome.tsx +0 -102
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ThinkingLevel } from '@/shared/lib/api';
|
|
2
|
+
import {
|
|
3
|
+
THINKING_LEVELS,
|
|
4
|
+
toProviderLocalModelId,
|
|
5
|
+
type ModelConfig
|
|
6
|
+
} from './provider-form-support.utils';
|
|
7
|
+
|
|
8
|
+
type AddProviderModelResult = {
|
|
9
|
+
models: string[];
|
|
10
|
+
draft: string;
|
|
11
|
+
errorKey?: 'providerModelInvalidProviderPrefix';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function addProviderLocalModel(models: string[], draft: string, aliases: string[]): AddProviderModelResult {
|
|
15
|
+
const next = toProviderLocalModelId(draft, aliases);
|
|
16
|
+
if (!next) {
|
|
17
|
+
return { models, draft };
|
|
18
|
+
}
|
|
19
|
+
if (next.includes('/')) {
|
|
20
|
+
return { models, draft, errorKey: 'providerModelInvalidProviderPrefix' };
|
|
21
|
+
}
|
|
22
|
+
if (models.includes(next)) {
|
|
23
|
+
return { models, draft: '' };
|
|
24
|
+
}
|
|
25
|
+
return { models: [...models, next], draft: '' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function removeProviderLocalModel(models: string[], modelConfig: ModelConfig, modelName: string): {
|
|
29
|
+
models: string[];
|
|
30
|
+
modelConfig: ModelConfig;
|
|
31
|
+
} {
|
|
32
|
+
const nextConfig = { ...modelConfig };
|
|
33
|
+
delete nextConfig[modelName];
|
|
34
|
+
return {
|
|
35
|
+
models: models.filter((name) => name !== modelName),
|
|
36
|
+
modelConfig: nextConfig
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function toggleModelThinkingLevelInConfig(modelConfig: ModelConfig, modelName: string, level: ThinkingLevel): ModelConfig {
|
|
41
|
+
const currentEntry = modelConfig[modelName];
|
|
42
|
+
const currentLevels = currentEntry?.thinking?.supported ?? [];
|
|
43
|
+
const nextLevels = currentLevels.includes(level)
|
|
44
|
+
? currentLevels.filter((item) => item !== level)
|
|
45
|
+
: THINKING_LEVELS.filter((item) => item === level || currentLevels.includes(item));
|
|
46
|
+
const nextDefault =
|
|
47
|
+
currentEntry?.thinking?.default && nextLevels.includes(currentEntry.thinking.default)
|
|
48
|
+
? currentEntry.thinking.default
|
|
49
|
+
: undefined;
|
|
50
|
+
const nextEntry = {
|
|
51
|
+
...currentEntry,
|
|
52
|
+
thinking:
|
|
53
|
+
nextLevels.length > 0
|
|
54
|
+
? nextDefault
|
|
55
|
+
? { supported: nextLevels, default: nextDefault }
|
|
56
|
+
: { supported: nextLevels }
|
|
57
|
+
: undefined
|
|
58
|
+
};
|
|
59
|
+
const next = { ...modelConfig };
|
|
60
|
+
if (nextEntry.thinking || nextEntry.vision === true) {
|
|
61
|
+
next[modelName] = nextEntry;
|
|
62
|
+
} else {
|
|
63
|
+
delete next[modelName];
|
|
64
|
+
}
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function setModelThinkingDefaultInConfig(modelConfig: ModelConfig, modelName: string, level: ThinkingLevel | null): ModelConfig {
|
|
69
|
+
const currentEntry = modelConfig[modelName];
|
|
70
|
+
const currentThinking = currentEntry?.thinking;
|
|
71
|
+
if (!currentThinking || currentThinking.supported.length === 0) {
|
|
72
|
+
return modelConfig;
|
|
73
|
+
}
|
|
74
|
+
if (level && !currentThinking.supported.includes(level)) {
|
|
75
|
+
return modelConfig;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
...modelConfig,
|
|
79
|
+
[modelName]: {
|
|
80
|
+
...currentEntry,
|
|
81
|
+
thinking: level
|
|
82
|
+
? { supported: currentThinking.supported, default: level }
|
|
83
|
+
: { supported: currentThinking.supported }
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function setModelVisionInConfig(modelConfig: ModelConfig, modelName: string, vision: boolean): ModelConfig {
|
|
89
|
+
const currentEntry = modelConfig[modelName];
|
|
90
|
+
const nextEntry = {
|
|
91
|
+
...currentEntry,
|
|
92
|
+
vision: vision ? true : undefined
|
|
93
|
+
};
|
|
94
|
+
const next = { ...modelConfig };
|
|
95
|
+
if (nextEntry.thinking || nextEntry.vision === true) {
|
|
96
|
+
next[modelName] = nextEntry;
|
|
97
|
+
} else {
|
|
98
|
+
delete next[modelName];
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { t } from '@/shared/lib/i18n';
|
|
2
2
|
import type { getLanguage } from '@/shared/lib/i18n';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ProviderConfigUpdate,
|
|
5
|
+
ProviderConfigView,
|
|
6
|
+
ProviderConnectionTestRequest,
|
|
7
|
+
ThinkingLevel
|
|
8
|
+
} from '@/shared/lib/api';
|
|
4
9
|
import {
|
|
5
10
|
normalizeModelConfigMap,
|
|
6
11
|
normalizeThinkingLevels,
|
|
@@ -14,6 +19,35 @@ type ModelConfig = Record<string, ModelConfigEntry>;
|
|
|
14
19
|
type ProviderAuthMethodOption = {
|
|
15
20
|
id: string;
|
|
16
21
|
};
|
|
22
|
+
type ProviderFormChangeInput = {
|
|
23
|
+
providerName?: string;
|
|
24
|
+
apiKey: string;
|
|
25
|
+
apiBase: string;
|
|
26
|
+
currentApiBase: string;
|
|
27
|
+
extraHeaders: Record<string, string> | null;
|
|
28
|
+
currentHeaders: Record<string, string> | null;
|
|
29
|
+
supportsWireApi: boolean;
|
|
30
|
+
wireApi: WireApiType;
|
|
31
|
+
currentWireApi: WireApiType;
|
|
32
|
+
models: string[];
|
|
33
|
+
currentEditableModels: string[];
|
|
34
|
+
modelConfig: ModelConfig;
|
|
35
|
+
currentModelConfig: ModelConfig;
|
|
36
|
+
providerDisplayName: string;
|
|
37
|
+
effectiveDisplayName: string;
|
|
38
|
+
};
|
|
39
|
+
type ProviderSavePayloadInput = ProviderFormChangeInput & {
|
|
40
|
+
defaultApiBase: string;
|
|
41
|
+
};
|
|
42
|
+
type ProviderConnectionTestPayloadInput = {
|
|
43
|
+
apiKey: string;
|
|
44
|
+
apiBase: string;
|
|
45
|
+
extraHeaders: Record<string, string> | null;
|
|
46
|
+
supportsWireApi: boolean;
|
|
47
|
+
wireApi: WireApiType;
|
|
48
|
+
models: string[];
|
|
49
|
+
providerModelAliases: string[];
|
|
50
|
+
};
|
|
17
51
|
|
|
18
52
|
const EMPTY_PROVIDER_CONFIG: ProviderConfigView = {
|
|
19
53
|
providerId: '',
|
|
@@ -196,6 +230,70 @@ function modelConfigEqual(left: ModelConfig, right: ModelConfig): boolean {
|
|
|
196
230
|
return true;
|
|
197
231
|
}
|
|
198
232
|
|
|
233
|
+
function hasProviderFormChanges(input: ProviderFormChangeInput): boolean {
|
|
234
|
+
if (!input.providerName) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
return (
|
|
238
|
+
input.apiKey.trim().length > 0 ||
|
|
239
|
+
input.apiBase.trim() !== input.currentApiBase.trim() ||
|
|
240
|
+
!headersEqual(input.extraHeaders, input.currentHeaders) ||
|
|
241
|
+
(input.supportsWireApi && input.wireApi !== input.currentWireApi) ||
|
|
242
|
+
!modelListsEqual(input.models, input.currentEditableModels) ||
|
|
243
|
+
!modelConfigEqual(input.modelConfig, input.currentModelConfig) ||
|
|
244
|
+
input.providerDisplayName.trim() !== input.effectiveDisplayName
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function buildProviderSavePayload(input: ProviderSavePayloadInput): ProviderConfigUpdate {
|
|
249
|
+
const payload: ProviderConfigUpdate = {};
|
|
250
|
+
const trimmedApiKey = input.apiKey.trim();
|
|
251
|
+
const trimmedApiBase = input.apiBase.trim();
|
|
252
|
+
const normalizedHeaders = normalizeHeaders(input.extraHeaders);
|
|
253
|
+
const trimmedDisplayName = input.providerDisplayName.trim();
|
|
254
|
+
|
|
255
|
+
if (trimmedDisplayName !== input.effectiveDisplayName) {
|
|
256
|
+
payload.displayName = trimmedDisplayName.length > 0 ? trimmedDisplayName : null;
|
|
257
|
+
}
|
|
258
|
+
if (trimmedApiKey.length > 0) {
|
|
259
|
+
payload.apiKey = trimmedApiKey;
|
|
260
|
+
}
|
|
261
|
+
if (trimmedApiBase !== input.currentApiBase.trim()) {
|
|
262
|
+
payload.apiBase = trimmedApiBase.length > 0 && trimmedApiBase !== input.defaultApiBase ? trimmedApiBase : null;
|
|
263
|
+
}
|
|
264
|
+
if (!headersEqual(normalizedHeaders, input.currentHeaders)) {
|
|
265
|
+
payload.extraHeaders = normalizedHeaders;
|
|
266
|
+
}
|
|
267
|
+
if (input.supportsWireApi && input.wireApi !== input.currentWireApi) {
|
|
268
|
+
payload.wireApi = input.wireApi;
|
|
269
|
+
}
|
|
270
|
+
if (!modelListsEqual(input.models, input.currentEditableModels)) {
|
|
271
|
+
payload.models = serializeModelsForSave(input.models, input.providerName ?? '');
|
|
272
|
+
}
|
|
273
|
+
if (!modelConfigEqual(input.modelConfig, input.currentModelConfig)) {
|
|
274
|
+
payload.modelConfig = normalizeModelConfigForModels(input.modelConfig, input.models);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return payload;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function buildProviderConnectionTestPayload(input: ProviderConnectionTestPayloadInput): ProviderConnectionTestRequest {
|
|
281
|
+
const preferredModel = input.models.find((modelName) => modelName.trim().length > 0) ?? '';
|
|
282
|
+
const testModel = toProviderLocalModelId(preferredModel, input.providerModelAliases);
|
|
283
|
+
const payload: ProviderConnectionTestRequest = {
|
|
284
|
+
apiBase: input.apiBase.trim(),
|
|
285
|
+
extraHeaders: normalizeHeaders(input.extraHeaders),
|
|
286
|
+
model: testModel || null
|
|
287
|
+
};
|
|
288
|
+
if (input.apiKey.trim().length > 0) {
|
|
289
|
+
payload.apiKey = input.apiKey.trim();
|
|
290
|
+
}
|
|
291
|
+
if (input.supportsWireApi) {
|
|
292
|
+
payload.wireApi = input.wireApi;
|
|
293
|
+
}
|
|
294
|
+
return payload;
|
|
295
|
+
}
|
|
296
|
+
|
|
199
297
|
function formatThinkingLevelLabel(level: ThinkingLevel): string {
|
|
200
298
|
if (level === 'off') {
|
|
201
299
|
return t('chatThinkingLevelOff');
|
|
@@ -285,8 +383,11 @@ function shouldUsePillSelector(params: {
|
|
|
285
383
|
|
|
286
384
|
export type { ModelConfig, ProviderAuthMethodOption, WireApiType };
|
|
287
385
|
export {
|
|
386
|
+
buildProviderConnectionTestPayload,
|
|
387
|
+
buildProviderSavePayload,
|
|
288
388
|
EMPTY_PROVIDER_CONFIG,
|
|
289
389
|
formatThinkingLevelLabel,
|
|
390
|
+
hasProviderFormChanges,
|
|
290
391
|
headersEqual,
|
|
291
392
|
modelListsEqual,
|
|
292
393
|
modelConfigEqual,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { SecretProviderView, SecretRefView, SecretSourceView, SecretsView } from '@/shared/lib/api';
|
|
2
|
+
import { t } from '@/shared/lib/i18n';
|
|
3
|
+
|
|
4
|
+
export type ProviderRow = {
|
|
5
|
+
alias: string;
|
|
6
|
+
source: SecretSourceView;
|
|
7
|
+
prefix: string;
|
|
8
|
+
path: string;
|
|
9
|
+
command: string;
|
|
10
|
+
argsText: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
timeoutMs: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type RefRow = {
|
|
16
|
+
path: string;
|
|
17
|
+
source: SecretSourceView;
|
|
18
|
+
provider: string;
|
|
19
|
+
id: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SecretsFormState = {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
defaultEnv: string;
|
|
25
|
+
defaultFile: string;
|
|
26
|
+
defaultExec: string;
|
|
27
|
+
providers: ProviderRow[];
|
|
28
|
+
refs: RefRow[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const SOURCE_OPTIONS: SecretSourceView[] = ['env', 'file', 'exec'];
|
|
32
|
+
|
|
33
|
+
export function createProviderRow(alias = ''): ProviderRow {
|
|
34
|
+
return { alias, source: 'env', prefix: '', path: '', command: '', argsText: '', cwd: '', timeoutMs: 5000 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createRefRow(): RefRow {
|
|
38
|
+
return { path: '', source: 'env', provider: '', id: '' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function providerToRow(alias: string, provider: SecretProviderView): ProviderRow {
|
|
42
|
+
if (provider.source === 'env') return { ...createProviderRow(alias), source: 'env', prefix: provider.prefix ?? '' };
|
|
43
|
+
if (provider.source === 'file') return { ...createProviderRow(alias), source: 'file', path: provider.path };
|
|
44
|
+
return {
|
|
45
|
+
...createProviderRow(alias),
|
|
46
|
+
source: 'exec',
|
|
47
|
+
command: provider.command,
|
|
48
|
+
argsText: (provider.args ?? []).join('\n'),
|
|
49
|
+
cwd: provider.cwd ?? '',
|
|
50
|
+
timeoutMs: provider.timeoutMs ?? 5000,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function rowToProvider(row: ProviderRow): SecretProviderView {
|
|
55
|
+
if (row.source === 'env') return { source: 'env', ...(row.prefix.trim() ? { prefix: row.prefix.trim() } : {}) };
|
|
56
|
+
if (row.source === 'file') return { source: 'file', path: row.path.trim(), format: 'json' };
|
|
57
|
+
return {
|
|
58
|
+
source: 'exec',
|
|
59
|
+
command: row.command.trim(),
|
|
60
|
+
args: row.argsText.split('\n').map((item) => item.trim()).filter(Boolean),
|
|
61
|
+
...(row.cwd.trim() ? { cwd: row.cwd.trim() } : {}),
|
|
62
|
+
timeoutMs: Math.max(1, Math.trunc(row.timeoutMs || 5000)),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function buildSecretsFormState(secrets?: SecretsView): SecretsFormState {
|
|
67
|
+
if (!secrets) {
|
|
68
|
+
return { enabled: true, defaultEnv: '', defaultFile: '', defaultExec: '', providers: [], refs: [] };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
enabled: Boolean(secrets.enabled),
|
|
72
|
+
defaultEnv: secrets.defaults.env ?? '',
|
|
73
|
+
defaultFile: secrets.defaults.file ?? '',
|
|
74
|
+
defaultExec: secrets.defaults.exec ?? '',
|
|
75
|
+
providers: Object.entries(secrets.providers).map(([alias, provider]) => providerToRow(alias, provider)),
|
|
76
|
+
refs: Object.entries(secrets.refs).map(([path, ref]) => ({
|
|
77
|
+
path,
|
|
78
|
+
source: ref.source,
|
|
79
|
+
provider: ref.provider ?? '',
|
|
80
|
+
id: ref.id,
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function prepareSecretsFormSubmitState(state: SecretsFormState): SecretsFormState {
|
|
86
|
+
const providerMap: Record<string, SecretProviderView> = {};
|
|
87
|
+
for (const [index, row] of state.providers.entries()) {
|
|
88
|
+
const alias = row.alias.trim();
|
|
89
|
+
if (!alias) throw new Error(`${t('providerAlias')} #${index + 1} ${t('isRequired')}`);
|
|
90
|
+
if (providerMap[alias]) throw new Error(`${t('providerAlias')}: ${alias} (${t('duplicate')})`);
|
|
91
|
+
if (row.source === 'file' && !row.path.trim()) throw new Error(`${t('secretFilePath')} #${index + 1} ${t('isRequired')}`);
|
|
92
|
+
if (row.source === 'exec' && !row.command.trim()) throw new Error(`${t('secretExecCommand')} #${index + 1} ${t('isRequired')}`);
|
|
93
|
+
providerMap[alias] = rowToProvider(row);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const refMap: Record<string, SecretRefView> = {};
|
|
97
|
+
for (const [index, row] of state.refs.entries()) {
|
|
98
|
+
const path = row.path.trim();
|
|
99
|
+
const id = row.id.trim();
|
|
100
|
+
if (!path) throw new Error(`${t('secretConfigPath')} #${index + 1} ${t('isRequired')}`);
|
|
101
|
+
if (!id) throw new Error(`${t('secretId')} #${index + 1} ${t('isRequired')}`);
|
|
102
|
+
const provider = row.provider.trim();
|
|
103
|
+
if (provider && !providerMap[provider]) throw new Error(`${t('secretProviderAlias')}: ${provider} ${t('notFound')}`);
|
|
104
|
+
refMap[path] = { source: row.source, ...(provider ? { provider } : {}), id };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
enabled: state.enabled,
|
|
109
|
+
defaultEnv: state.defaultEnv.trim(),
|
|
110
|
+
defaultFile: state.defaultFile.trim(),
|
|
111
|
+
defaultExec: state.defaultExec.trim(),
|
|
112
|
+
providers: Object.entries(providerMap).map(([alias, provider]) => providerToRow(alias, provider)),
|
|
113
|
+
refs: Object.entries(refMap).map(([path, ref]) => ({ path, source: ref.source, provider: ref.provider ?? '', id: ref.id })),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildSecretsSubmitPayload(state: SecretsFormState) {
|
|
118
|
+
return {
|
|
119
|
+
enabled: state.enabled,
|
|
120
|
+
defaults: {
|
|
121
|
+
env: state.defaultEnv || null,
|
|
122
|
+
file: state.defaultFile || null,
|
|
123
|
+
exec: state.defaultExec || null,
|
|
124
|
+
},
|
|
125
|
+
providers: Object.fromEntries(
|
|
126
|
+
state.providers.map((row) => [row.alias.trim(), rowToProvider(row)]).filter(([alias]) => alias),
|
|
127
|
+
),
|
|
128
|
+
refs: Object.fromEntries(
|
|
129
|
+
state.refs.map((row) => [
|
|
130
|
+
row.path.trim(),
|
|
131
|
+
{
|
|
132
|
+
source: row.source,
|
|
133
|
+
...(row.provider.trim() ? { provider: row.provider.trim() } : {}),
|
|
134
|
+
id: row.id.trim(),
|
|
135
|
+
},
|
|
136
|
+
]).filter(([path, ref]) => path && (ref as SecretRefView).id),
|
|
137
|
+
),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -7,7 +7,13 @@ import { Switch } from '@/shared/components/ui/switch';
|
|
|
7
7
|
import { t } from '@/shared/lib/i18n';
|
|
8
8
|
import { parseOptionalInt } from '@/features/system-status/utils/runtime-config-agent.utils';
|
|
9
9
|
|
|
10
|
-
export function RuntimeAgentListCard(
|
|
10
|
+
export function RuntimeAgentListCard({
|
|
11
|
+
agents,
|
|
12
|
+
onAddAgent,
|
|
13
|
+
onRemoveAgent,
|
|
14
|
+
onSetDefaultAgent,
|
|
15
|
+
onUpdateAgent
|
|
16
|
+
}: {
|
|
11
17
|
agents: AgentProfileView[];
|
|
12
18
|
onUpdateAgent: (index: number, patch: Partial<AgentProfileView>) => void;
|
|
13
19
|
onRemoveAgent: (index: number) => void;
|
|
@@ -21,44 +27,44 @@ export function RuntimeAgentListCard(props: {
|
|
|
21
27
|
<CardDescription>{t('agentListHelp')}</CardDescription>
|
|
22
28
|
</CardHeader>
|
|
23
29
|
<CardContent className="space-y-3">
|
|
24
|
-
{
|
|
30
|
+
{agents.map((agent, index) => (
|
|
25
31
|
<div key={`${index}-${agent.id}`} className="rounded-xl border border-gray-200 p-3 space-y-3">
|
|
26
32
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
27
|
-
<Input value={agent.id} onChange={(event) =>
|
|
28
|
-
<Input value={agent.workspace ?? ''} onChange={(event) =>
|
|
29
|
-
<Input value={agent.model ?? ''} onChange={(event) =>
|
|
30
|
-
<Input value={agent.runtime ?? agent.engine ?? ''} onChange={(event) =>
|
|
33
|
+
<Input value={agent.id} onChange={(event) => onUpdateAgent(index, { id: event.target.value })} placeholder={t('agentIdPlaceholder')} />
|
|
34
|
+
<Input value={agent.workspace ?? ''} onChange={(event) => onUpdateAgent(index, { workspace: event.target.value })} placeholder={t('workspaceOverridePlaceholder')} />
|
|
35
|
+
<Input value={agent.model ?? ''} onChange={(event) => onUpdateAgent(index, { model: event.target.value })} placeholder={t('modelOverridePlaceholder')} />
|
|
36
|
+
<Input value={agent.runtime ?? agent.engine ?? ''} onChange={(event) => onUpdateAgent(index, { runtime: event.target.value })} placeholder={t('engineOverridePlaceholder')} />
|
|
31
37
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
32
38
|
<Input
|
|
33
39
|
type="number"
|
|
34
40
|
min={1000}
|
|
35
41
|
step={1000}
|
|
36
42
|
value={agent.contextTokens ?? ''}
|
|
37
|
-
onChange={(event) =>
|
|
43
|
+
onChange={(event) => onUpdateAgent(index, { contextTokens: parseOptionalInt(event.target.value) })}
|
|
38
44
|
placeholder={t('contextTokensPlaceholder')}
|
|
39
45
|
/>
|
|
40
46
|
<Input
|
|
41
47
|
type="number"
|
|
42
48
|
min={1}
|
|
43
49
|
value={agent.maxToolIterations ?? ''}
|
|
44
|
-
onChange={(event) =>
|
|
50
|
+
onChange={(event) => onUpdateAgent(index, { maxToolIterations: parseOptionalInt(event.target.value) })}
|
|
45
51
|
placeholder={t('maxToolsPlaceholder')}
|
|
46
52
|
/>
|
|
47
53
|
</div>
|
|
48
54
|
</div>
|
|
49
55
|
<div className="flex items-center justify-between">
|
|
50
56
|
<div className="flex items-center gap-2 text-sm text-gray-600">
|
|
51
|
-
<Switch checked={Boolean(agent.default)} onCheckedChange={(checked) =>
|
|
57
|
+
<Switch checked={Boolean(agent.default)} onCheckedChange={(checked) => onSetDefaultAgent(index, checked)} />
|
|
52
58
|
<span>{t('defaultAgent')}</span>
|
|
53
59
|
</div>
|
|
54
|
-
<Button type="button" variant="outline" size="sm" onClick={() =>
|
|
60
|
+
<Button type="button" variant="outline" size="sm" onClick={() => onRemoveAgent(index)}>
|
|
55
61
|
<Trash2 className="h-4 w-4 mr-1" />
|
|
56
62
|
{t('remove')}
|
|
57
63
|
</Button>
|
|
58
64
|
</div>
|
|
59
65
|
</div>
|
|
60
66
|
))}
|
|
61
|
-
<Button type="button" variant="outline" onClick={
|
|
67
|
+
<Button type="button" variant="outline" onClick={onAddAgent}>
|
|
62
68
|
<Plus className="h-4 w-4 mr-2" />
|
|
63
69
|
{t('addAgent')}
|
|
64
70
|
</Button>
|
|
@@ -7,7 +7,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|
|
7
7
|
import { t } from '@/shared/lib/i18n';
|
|
8
8
|
import type { PeerKind } from '@/features/system-status/utils/runtime-config-agent.utils';
|
|
9
9
|
|
|
10
|
-
export function RuntimeBindingListCard(
|
|
10
|
+
export function RuntimeBindingListCard({
|
|
11
|
+
bindings,
|
|
12
|
+
onAddBinding,
|
|
13
|
+
onRemoveBinding,
|
|
14
|
+
onUpdateBinding
|
|
15
|
+
}: {
|
|
11
16
|
bindings: AgentBindingView[];
|
|
12
17
|
onUpdateBinding: (index: number, next: AgentBindingView) => void;
|
|
13
18
|
onRemoveBinding: (index: number) => void;
|
|
@@ -20,19 +25,19 @@ export function RuntimeBindingListCard(props: {
|
|
|
20
25
|
<CardDescription>{t('bindingsHelp')}</CardDescription>
|
|
21
26
|
</CardHeader>
|
|
22
27
|
<CardContent className="space-y-3">
|
|
23
|
-
{
|
|
28
|
+
{bindings.map((binding, index) => {
|
|
24
29
|
const peerKind = (binding.match.peer?.kind ?? '') as PeerKind;
|
|
25
30
|
return (
|
|
26
31
|
<div key={`${index}-${binding.agentId}`} className="rounded-xl border border-gray-200 p-3 space-y-3">
|
|
27
32
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
28
|
-
<Input value={binding.agentId} onChange={(event) =>
|
|
29
|
-
<Input value={binding.match.channel} onChange={(event) =>
|
|
30
|
-
<Input value={binding.match.accountId ?? ''} onChange={(event) =>
|
|
33
|
+
<Input value={binding.agentId} onChange={(event) => onUpdateBinding(index, { ...binding, agentId: event.target.value })} placeholder={t('targetAgentIdPlaceholder')} />
|
|
34
|
+
<Input value={binding.match.channel} onChange={(event) => onUpdateBinding(index, { ...binding, match: { ...binding.match, channel: event.target.value } })} placeholder={t('channelPlaceholder')} />
|
|
35
|
+
<Input value={binding.match.accountId ?? ''} onChange={(event) => onUpdateBinding(index, { ...binding, match: { ...binding.match, accountId: event.target.value } })} placeholder={t('accountIdOptionalPlaceholder')} />
|
|
31
36
|
<Select
|
|
32
37
|
value={peerKind || '__none__'}
|
|
33
38
|
onValueChange={(value) => {
|
|
34
39
|
const nextKind = value === '__none__' ? '' : (value as PeerKind);
|
|
35
|
-
|
|
40
|
+
onUpdateBinding(
|
|
36
41
|
index,
|
|
37
42
|
nextKind
|
|
38
43
|
? { ...binding, match: { ...binding.match, peer: { kind: nextKind, id: binding.match.peer?.id ?? '' } } }
|
|
@@ -53,7 +58,7 @@ export function RuntimeBindingListCard(props: {
|
|
|
53
58
|
<Input
|
|
54
59
|
value={binding.match.peer?.id ?? ''}
|
|
55
60
|
onChange={(event) =>
|
|
56
|
-
|
|
61
|
+
onUpdateBinding(index, {
|
|
57
62
|
...binding,
|
|
58
63
|
match: {
|
|
59
64
|
...binding.match,
|
|
@@ -65,7 +70,7 @@ export function RuntimeBindingListCard(props: {
|
|
|
65
70
|
/>
|
|
66
71
|
</div>
|
|
67
72
|
<div className="flex justify-end">
|
|
68
|
-
<Button type="button" variant="outline" size="sm" onClick={() =>
|
|
73
|
+
<Button type="button" variant="outline" size="sm" onClick={() => onRemoveBinding(index)}>
|
|
69
74
|
<Trash2 className="h-4 w-4 mr-1" />
|
|
70
75
|
{t('remove')}
|
|
71
76
|
</Button>
|
|
@@ -73,7 +78,7 @@ export function RuntimeBindingListCard(props: {
|
|
|
73
78
|
</div>
|
|
74
79
|
);
|
|
75
80
|
})}
|
|
76
|
-
<Button type="button" variant="outline" onClick={
|
|
81
|
+
<Button type="button" variant="outline" onClick={onAddBinding}>
|
|
77
82
|
<Plus className="h-4 w-4 mr-2" />
|
|
78
83
|
{t('addBinding')}
|
|
79
84
|
</Button>
|
|
@@ -24,11 +24,14 @@ type UpdateRuntimeMutation = {
|
|
|
24
24
|
isPending: boolean;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
export function RuntimeConfigEditor(
|
|
27
|
+
export function RuntimeConfigEditor({
|
|
28
|
+
config,
|
|
29
|
+
updateRuntime
|
|
30
|
+
}: {
|
|
28
31
|
config: ConfigView;
|
|
29
32
|
updateRuntime: UpdateRuntimeMutation;
|
|
30
33
|
}) {
|
|
31
|
-
const initialState = useMemo(() => createRuntimeConfigEditorState(
|
|
34
|
+
const initialState = useMemo(() => createRuntimeConfigEditorState(config), [config]);
|
|
32
35
|
const [agents, setAgents] = useState(initialState.agents);
|
|
33
36
|
const [bindings, setBindings] = useState(initialState.bindings);
|
|
34
37
|
const [runtimeEntries, setRuntimeEntries] = useState(initialState.runtimeEntries);
|
|
@@ -68,7 +71,7 @@ export function RuntimeConfigEditor(props: {
|
|
|
68
71
|
defaultEngine,
|
|
69
72
|
knownAgentIds
|
|
70
73
|
});
|
|
71
|
-
|
|
74
|
+
updateRuntime.mutate({ data });
|
|
72
75
|
} catch (error) {
|
|
73
76
|
toast.error(error instanceof Error ? error.message : String(error));
|
|
74
77
|
}
|
|
@@ -109,9 +112,9 @@ export function RuntimeConfigEditor(props: {
|
|
|
109
112
|
onAddBinding={() => setBindings((previous) => [...previous, createEmptyRuntimeBinding()])}
|
|
110
113
|
/>
|
|
111
114
|
<div className="flex justify-end">
|
|
112
|
-
<Button type="button" onClick={handleSave} disabled={
|
|
115
|
+
<Button type="button" onClick={handleSave} disabled={updateRuntime.isPending}>
|
|
113
116
|
<Save className="h-4 w-4 mr-2" />
|
|
114
|
-
{
|
|
117
|
+
{updateRuntime.isPending ? t('saving') : t('saveRuntimeSettings')}
|
|
115
118
|
</Button>
|
|
116
119
|
</div>
|
|
117
120
|
</PageLayout>
|
|
@@ -6,7 +6,12 @@ import { Switch } from '@/shared/components/ui/switch';
|
|
|
6
6
|
import { t } from '@/shared/lib/i18n';
|
|
7
7
|
import type { RuntimeEntryDraft } from '@/features/system-status/utils/runtime-config-agent.utils';
|
|
8
8
|
|
|
9
|
-
export function RuntimeEntryListCard(
|
|
9
|
+
export function RuntimeEntryListCard({
|
|
10
|
+
entries,
|
|
11
|
+
onAddEntry,
|
|
12
|
+
onRemoveEntry,
|
|
13
|
+
onUpdateEntry
|
|
14
|
+
}: {
|
|
10
15
|
entries: RuntimeEntryDraft[];
|
|
11
16
|
onUpdateEntry: (index: number, patch: Partial<RuntimeEntryDraft>) => void;
|
|
12
17
|
onRemoveEntry: (index: number) => void;
|
|
@@ -19,15 +24,15 @@ export function RuntimeEntryListCard(props: {
|
|
|
19
24
|
<CardDescription>{t('runtimeEntriesHelp')}</CardDescription>
|
|
20
25
|
</CardHeader>
|
|
21
26
|
<CardContent className="space-y-3">
|
|
22
|
-
{
|
|
27
|
+
{entries.map((entry, index) => (
|
|
23
28
|
<div key={`${index}-${entry.id || 'runtime-entry'}`} className="rounded-xl border border-gray-200 p-3 space-y-3">
|
|
24
29
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
25
|
-
<Input value={entry.id} onChange={(event) =>
|
|
26
|
-
<Input value={entry.label ?? ''} onChange={(event) =>
|
|
27
|
-
<Input value={entry.type} onChange={(event) =>
|
|
30
|
+
<Input value={entry.id} onChange={(event) => onUpdateEntry(index, { id: event.target.value })} placeholder={t('runtimeEntryIdPlaceholder')} />
|
|
31
|
+
<Input value={entry.label ?? ''} onChange={(event) => onUpdateEntry(index, { label: event.target.value })} placeholder={t('runtimeEntryLabelPlaceholder')} />
|
|
32
|
+
<Input value={entry.type} onChange={(event) => onUpdateEntry(index, { type: event.target.value })} placeholder={t('runtimeEntryTypePlaceholder')} />
|
|
28
33
|
<div className="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
|
|
29
34
|
<span className="text-sm text-gray-700">{t('enabled')}</span>
|
|
30
|
-
<Switch checked={entry.enabled !== false} onCheckedChange={(checked) =>
|
|
35
|
+
<Switch checked={entry.enabled !== false} onCheckedChange={(checked) => onUpdateEntry(index, { enabled: checked })} />
|
|
31
36
|
</div>
|
|
32
37
|
</div>
|
|
33
38
|
<div className="space-y-2">
|
|
@@ -35,19 +40,19 @@ export function RuntimeEntryListCard(props: {
|
|
|
35
40
|
<textarea
|
|
36
41
|
className="min-h-32 w-full rounded-md border border-gray-200 px-3 py-2 text-sm font-mono"
|
|
37
42
|
value={entry.configText}
|
|
38
|
-
onChange={(event) =>
|
|
43
|
+
onChange={(event) => onUpdateEntry(index, { configText: event.target.value })}
|
|
39
44
|
spellCheck={false}
|
|
40
45
|
/>
|
|
41
46
|
</div>
|
|
42
47
|
<div className="flex justify-end">
|
|
43
|
-
<Button type="button" variant="outline" onClick={() =>
|
|
48
|
+
<Button type="button" variant="outline" onClick={() => onRemoveEntry(index)}>
|
|
44
49
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
45
50
|
{t('delete')}
|
|
46
51
|
</Button>
|
|
47
52
|
</div>
|
|
48
53
|
</div>
|
|
49
54
|
))}
|
|
50
|
-
<Button type="button" variant="outline" onClick={
|
|
55
|
+
<Button type="button" variant="outline" onClick={onAddEntry}>
|
|
51
56
|
<Plus className="mr-2 h-4 w-4" />
|
|
52
57
|
{t('addRuntimeEntry')}
|
|
53
58
|
</Button>
|
|
@@ -12,7 +12,16 @@ const DM_SCOPE_OPTIONS: Array<{ value: DmScope; labelKey: string }> = [
|
|
|
12
12
|
{ value: 'per-account-channel-peer', labelKey: 'dmScopePerAccountChannelPeer' }
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
export function RuntimeSettingsCard(
|
|
15
|
+
export function RuntimeSettingsCard({
|
|
16
|
+
companionEnabled,
|
|
17
|
+
defaultContextTokens,
|
|
18
|
+
defaultEngine,
|
|
19
|
+
dmScope,
|
|
20
|
+
onCompanionEnabledChange,
|
|
21
|
+
onDefaultContextTokensChange,
|
|
22
|
+
onDefaultEngineChange,
|
|
23
|
+
onDmScopeChange
|
|
24
|
+
}: {
|
|
16
25
|
dmScope: DmScope;
|
|
17
26
|
companionEnabled: boolean;
|
|
18
27
|
defaultContextTokens: number;
|
|
@@ -34,7 +43,7 @@ export function RuntimeSettingsCard(props: {
|
|
|
34
43
|
<div className="text-sm font-medium text-gray-800">{t('runtimeCompanionEnabled')}</div>
|
|
35
44
|
<p className="text-xs text-gray-500">{t('runtimeCompanionEnabledHelp')}</p>
|
|
36
45
|
</div>
|
|
37
|
-
<Switch checked={
|
|
46
|
+
<Switch checked={companionEnabled} onCheckedChange={onCompanionEnabledChange} />
|
|
38
47
|
</div>
|
|
39
48
|
<div className="space-y-2">
|
|
40
49
|
<label className="text-sm font-medium text-gray-800">{t('defaultContextTokens')}</label>
|
|
@@ -42,23 +51,23 @@ export function RuntimeSettingsCard(props: {
|
|
|
42
51
|
type="number"
|
|
43
52
|
min={1000}
|
|
44
53
|
step={1000}
|
|
45
|
-
value={
|
|
46
|
-
onChange={(event) =>
|
|
54
|
+
value={defaultContextTokens}
|
|
55
|
+
onChange={(event) => onDefaultContextTokensChange(Math.max(1000, Number.parseInt(event.target.value, 10) || 1000))}
|
|
47
56
|
/>
|
|
48
57
|
<p className="text-xs text-gray-500">{t('defaultContextTokensHelp')}</p>
|
|
49
58
|
</div>
|
|
50
59
|
<div className="space-y-2">
|
|
51
60
|
<label className="text-sm font-medium text-gray-800">{t('defaultEngine')}</label>
|
|
52
61
|
<Input
|
|
53
|
-
value={
|
|
54
|
-
onChange={(event) =>
|
|
62
|
+
value={defaultEngine}
|
|
63
|
+
onChange={(event) => onDefaultEngineChange(event.target.value)}
|
|
55
64
|
placeholder={t('defaultEnginePlaceholder')}
|
|
56
65
|
/>
|
|
57
66
|
<p className="text-xs text-gray-500">{t('defaultEngineHelp')}</p>
|
|
58
67
|
</div>
|
|
59
68
|
<div className="space-y-2">
|
|
60
69
|
<label className="text-sm font-medium text-gray-800">{t('dmScope')}</label>
|
|
61
|
-
<Select value={
|
|
70
|
+
<Select value={dmScope} onValueChange={(value) => onDmScopeChange(value as DmScope)}>
|
|
62
71
|
<SelectTrigger>
|
|
63
72
|
<SelectValue />
|
|
64
73
|
</SelectTrigger>
|