diff --git a/plane-src/apps/web/core/components/workspace/settings/ai-voice-tasker-settings.tsx b/plane-src/apps/web/core/components/workspace/settings/ai-voice-tasker-settings.tsx index d53b5e0..372be16 100644 --- a/plane-src/apps/web/core/components/workspace/settings/ai-voice-tasker-settings.tsx +++ b/plane-src/apps/web/core/components/workspace/settings/ai-voice-tasker-settings.tsx @@ -112,8 +112,22 @@ const ACCESS_MODE_OPTIONS: { }, ]; +type TVoiceTaskerSettingsTab = "access" | "limits" | "models" | "monitor"; + +const VOICE_TASKER_SETTINGS_TABS: { + icon: ElementType; + label: string; + value: TVoiceTaskerSettingsTab; +}[] = [ + { value: "access", label: "Доступ", icon: ShieldCheck }, + { value: "limits", label: "Лимиты", icon: Activity }, + { value: "models", label: "Модели и ключ", icon: KeyRound }, + { value: "monitor", label: "Очередь и журнал", icon: BrainCircuit }, +]; + export const AIVoiceTaskerSettingsContent = observer(function AIVoiceTaskerSettingsContent(props: TProps) { const { showHeading = true, workspaceSlug } = props; + const [activeTab, setActiveTab] = useState("access"); const [formState, setFormState] = useState(getInitialFormState()); const [isSaving, setIsSaving] = useState(false); const [isTesting, setIsTesting] = useState(false); @@ -325,177 +339,198 @@ export const AIVoiceTaskerSettingsContent = observer(function AIVoiceTaskerSetti /> } /> + - updateFormValue("access_mode", value)} - onToggleMember={(memberId) => toggleListValue("enabled_member_ids", memberId)} - onToggleProject={(projectId) => toggleListValue("enabled_project_ids", projectId)} - /> + -
- - + updateFormValue("access_mode", value)} + onToggleMember={(memberId) => toggleListValue("enabled_member_ids", memberId)} + onToggleProject={(projectId) => toggleListValue("enabled_project_ids", projectId)} + /> +
+ + + + - - - + + + updateFormValue("transcription_model", event.target.value)} + className="nodedc-settings-input w-full" + /> + + + updateFormValue("structuring_model", event.target.value)} + className="nodedc-settings-input w-full" + /> + + + updateFormValue("openai_api_key", event.target.value)} + placeholder={settings.credential.has_key ? "sk-... не изменять" : "sk-..."} + className="nodedc-settings-input w-full" + /> + +
+
+
- + Test connection + +
+ + )} -
- } + {activeTab === "monitor" && ( + -
- - updateFormValue("openai_api_key", event.target.value)} - placeholder={settings.credential.has_key ? "sk-... не изменять" : "sk-..."} - className="nodedc-settings-input w-full" - /> - - -
-
- -
- -
- - updateFormValue("transcription_model", event.target.value)} - className="nodedc-settings-input w-full" - /> - - - updateFormValue("structuring_model", event.target.value)} - className="nodedc-settings-input w-full" - /> - - - updateFormValue("per_user_hourly_limit", value)} - /> - - - updateFormValue("workspace_hourly_limit", value)} - /> - - - updateFormValue("per_user_daily_limit", value)} - /> - - - updateFormValue("workspace_daily_limit", value)} - /> - - - updateFormValue("project_daily_limit", value)} - /> - - - updateFormValue("workspace_concurrency_limit", value)} - /> - - - updateFormValue("sensitive_data_retention_days", value)} - /> - -
-
- - + )}
+ ); + })} +
+ ); +} + +type TFeatureScopeSummaryProps = { + accessMode: TWorkspaceAIAccessMode; + enabledMemberIds: string[]; + enabledProjectIds: string[]; +}; + +function FeatureScopeSummary({ accessMode, enabledMemberIds, enabledProjectIds }: TFeatureScopeSummaryProps) { + const summaryByMode: Record = { + all_workspace_members: { label: "Активный scope", value: "Весь workspace" }, + admins_only: { label: "Активный scope", value: "Только админы" }, + selected_projects: { label: "Выбранные контуры", value: formatMonitorNumber(enabledProjectIds.length) }, + selected_members: { label: "Выбранные пользователи", value: formatMonitorNumber(enabledMemberIds.length) }, + }; + const summary = summaryByMode[accessMode]; + + return ( +
+
{summary.label}
+
{summary.value}
+
backend проверка перед записью и публикацией
+
+ ); +} + +type TLimitUsageOverviewProps = { + formState: TFormState; + monitor?: TVoiceTaskMonitor; +}; + +function LimitUsageOverview({ formState, monitor }: TLimitUsageOverviewProps) { + const totalSessions = monitor?.summary.total ?? 0; + const activeSessions = monitor?.summary.active ?? 0; + const workspaceDailyRatio = getLimitRatio(totalSessions, formState.workspace_daily_limit); + const concurrencyRatio = getLimitRatio(activeSessions, formState.workspace_concurrency_limit); + + return ( +
+ + + + +
+ ); +} + +type TLimitUsageCardProps = { + label: string; + ratio: number; + value: string; +}; + +function LimitUsageCard({ label, ratio, value }: TLimitUsageCardProps) { + const boundedRatio = Math.min(Math.max(ratio, 0), 1); + + return ( +
+
{label}
+
{value}
+
+
+
+
+ ); +} + +function getLimitRatio(value: number, limit: number) { + if (!limit || limit <= 0) return 0; + return value / limit; +} + function AccessScopeSection({ accessMode, enabledMemberIds, @@ -545,7 +702,7 @@ function AccessScopeSection({ const selectedMembersCount = enabledMemberIds.length; return ( -
+

Доступ к функции