mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-17 13:06:21 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e56dcf9538 |
@@ -411,12 +411,16 @@
|
||||
"rag.userQuery.actions.regenerate": "Regenerate Query",
|
||||
"regenerate": "Regenerate",
|
||||
"roleAndArchive": "Agent Profile & History",
|
||||
"runtimeEnv.device.empty": "No devices available. Connect to gateway from the desktop app first.",
|
||||
"runtimeEnv.mode.cloud": "Cloud Sandbox",
|
||||
"runtimeEnv.mode.cloudDesc": "Run in a secure cloud sandbox",
|
||||
"runtimeEnv.mode.local": "Local",
|
||||
"runtimeEnv.mode.localDesc": "Access local files and commands",
|
||||
"runtimeEnv.mode.none": "Off",
|
||||
"runtimeEnv.mode.noneDesc": "Disable runtime environment",
|
||||
"runtimeEnv.mode.sandbox": "Sandbox",
|
||||
"runtimeEnv.mode.sandboxDesc": "Run in an isolated cloud sandbox",
|
||||
"runtimeEnv.section.device": "Device",
|
||||
"runtimeEnv.selectMode": "Select Runtime Environment",
|
||||
"runtimeEnv.title": "Runtime Environment",
|
||||
"search.grounding.imageSearchQueries": "Image Search Keywords",
|
||||
|
||||
@@ -411,12 +411,16 @@
|
||||
"rag.userQuery.actions.regenerate": "重新生成 Query",
|
||||
"regenerate": "重新生成",
|
||||
"roleAndArchive": "助理档案与记录",
|
||||
"runtimeEnv.device.empty": "暂无可用设备,请先在桌面端连接到网关",
|
||||
"runtimeEnv.mode.cloud": "云端沙箱",
|
||||
"runtimeEnv.mode.cloudDesc": "在安全的云端沙箱中运行",
|
||||
"runtimeEnv.mode.local": "本地",
|
||||
"runtimeEnv.mode.localDesc": "访问本地文件和命令",
|
||||
"runtimeEnv.mode.none": "关闭",
|
||||
"runtimeEnv.mode.noneDesc": "禁用运行时环境",
|
||||
"runtimeEnv.mode.sandbox": "沙箱",
|
||||
"runtimeEnv.mode.sandboxDesc": "在隔离的云端沙箱中运行",
|
||||
"runtimeEnv.section.device": "设备",
|
||||
"runtimeEnv.selectMode": "选择运行环境",
|
||||
"runtimeEnv.title": "运行环境",
|
||||
"search.grounding.imageSearchQueries": "图片搜索关键词",
|
||||
|
||||
@@ -9,11 +9,12 @@ export type AgentMode = 'auto' | 'plan' | 'ask' | 'implement';
|
||||
|
||||
/**
|
||||
* Runtime environment mode
|
||||
* - local: Access local files and commands (desktop only)
|
||||
* - cloud: Run in cloud sandbox
|
||||
* - local: Run on a specific device (desktop only, requires deviceId)
|
||||
* - sandbox: Run in isolated cloud sandbox
|
||||
* - cloud: @deprecated Use 'sandbox' instead, kept for backward compatibility
|
||||
* - none: No runtime environment
|
||||
*/
|
||||
export type RuntimeEnvMode = 'cloud' | 'local' | 'none';
|
||||
export type RuntimeEnvMode = 'cloud' | 'local' | 'none' | 'sandbox';
|
||||
|
||||
export type RuntimePlatform = 'desktop' | 'web';
|
||||
|
||||
@@ -21,6 +22,11 @@ export type RuntimePlatform = 'desktop' | 'web';
|
||||
* Runtime environment configuration
|
||||
*/
|
||||
export interface RuntimeEnvConfig {
|
||||
/**
|
||||
* Device ID when runtimeMode is 'local' (desktop only).
|
||||
* Identifies which bound device to run on.
|
||||
*/
|
||||
deviceId?: string;
|
||||
/**
|
||||
* Runtime environment mode per platform
|
||||
*/
|
||||
|
||||
@@ -170,9 +170,10 @@ export interface LobeAgentChatConfig extends AgentMemoryChatConfig, AgentSelfIte
|
||||
/**
|
||||
* Zod schema for RuntimeEnvConfig
|
||||
*/
|
||||
const runtimeEnvModeEnum = z.enum(['local', 'cloud', 'none']);
|
||||
const runtimeEnvModeEnum = z.enum(['local', 'cloud', 'none', 'sandbox']);
|
||||
|
||||
export const RuntimeEnvConfigSchema = z.object({
|
||||
deviceId: z.string().optional(),
|
||||
runtimeMode: z.record(z.string(), runtimeEnvModeEnum).optional(),
|
||||
workingDirectory: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { type DeviceAttachment } from '@lobechat/builtin-tool-remote-device';
|
||||
import { Flexbox, Icon } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import { LaptopIcon, MonitorIcon, ServerIcon } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
deviceName: css`
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: ${cssVar.colorText};
|
||||
`,
|
||||
deviceOption: css`
|
||||
cursor: pointer;
|
||||
|
||||
width: 100%;
|
||||
padding-block: 8px;
|
||||
padding-inline: 8px;
|
||||
border-radius: ${cssVar.borderRadius};
|
||||
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: ${cssVar.colorFillTertiary};
|
||||
}
|
||||
`,
|
||||
deviceOptionActive: css`
|
||||
background: ${cssVar.colorFillTertiary};
|
||||
`,
|
||||
deviceOptionDesc: css`
|
||||
font-size: 12px;
|
||||
color: ${cssVar.colorTextDescription};
|
||||
`,
|
||||
deviceOptionIcon: css`
|
||||
flex-shrink: 0;
|
||||
border: 1px solid ${cssVar.colorFillTertiary};
|
||||
border-radius: ${cssVar.borderRadius};
|
||||
background: ${cssVar.colorBgElevated};
|
||||
`,
|
||||
sectionTitle: css`
|
||||
padding-block: 6px 2px;
|
||||
padding-inline: 8px;
|
||||
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: ${cssVar.colorTextQuaternary};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
`,
|
||||
}));
|
||||
|
||||
const PLATFORM_ICONS: Record<string, typeof LaptopIcon> = {
|
||||
darwin: LaptopIcon,
|
||||
linux: MonitorIcon,
|
||||
win32: MonitorIcon,
|
||||
};
|
||||
|
||||
interface DeviceSelectorProps {
|
||||
activeDeviceId?: string;
|
||||
devices: DeviceAttachment[];
|
||||
onSelect: (deviceId: string) => void;
|
||||
}
|
||||
|
||||
export const DeviceSelector = memo<DeviceSelectorProps>(
|
||||
({ activeDeviceId, devices, onSelect }) => {
|
||||
return (
|
||||
<>
|
||||
{devices.map((device) => {
|
||||
const IconComp = PLATFORM_ICONS[device.platform] || ServerIcon;
|
||||
const isActive = activeDeviceId === device.deviceId;
|
||||
|
||||
return (
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'flex-start'}
|
||||
className={cx(styles.deviceOption, isActive && styles.deviceOptionActive)}
|
||||
gap={12}
|
||||
key={device.deviceId}
|
||||
onClick={() => onSelect(device.deviceId)}
|
||||
>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={styles.deviceOptionIcon}
|
||||
height={32}
|
||||
justify={'center'}
|
||||
width={32}
|
||||
>
|
||||
<Icon icon={IconComp} size={16} />
|
||||
</Flexbox>
|
||||
<Flexbox flex={1}>
|
||||
<div className={styles.deviceName}>{device.hostname}</div>
|
||||
<div className={styles.deviceOptionDesc}>{device.platform}</div>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DeviceSelector.displayName = 'DeviceSelector';
|
||||
|
||||
/** Section header for device/sandbox/none groups */
|
||||
export const SectionHeader = memo<{ label: string }>(({ label }) => (
|
||||
<div className={styles.sectionTitle}>{label}</div>
|
||||
));
|
||||
|
||||
SectionHeader.displayName = 'SectionHeader';
|
||||
@@ -4,6 +4,7 @@ import { Github } from '@lobehub/icons';
|
||||
import { Flexbox, Icon, Popover, Skeleton, Tooltip } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar, cx } from 'antd-style';
|
||||
import {
|
||||
BoxIcon,
|
||||
ChevronDownIcon,
|
||||
CloudIcon,
|
||||
FolderIcon,
|
||||
@@ -12,9 +13,10 @@ import {
|
||||
MonitorOffIcon,
|
||||
SquircleDashed,
|
||||
} from 'lucide-react';
|
||||
import { memo, type ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import { memo, type ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { deviceService } from '@/services/device';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentByIdSelectors, chatConfigByIdSelectors } from '@/store/agent/selectors';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
@@ -26,6 +28,7 @@ import { useUpdateAgentConfig } from '../hooks/useUpdateAgentConfig';
|
||||
import { useChatInputStore } from '../store';
|
||||
import ApprovalMode from './ApprovalMode';
|
||||
import CloudRepoSwitcher from './CloudRepoSwitcher';
|
||||
import { DeviceSelector, SectionHeader } from './DeviceSelector';
|
||||
import GitStatus from './GitStatus';
|
||||
import ModeSelector from './ModeSelector';
|
||||
import { useRepoType } from './useRepoType';
|
||||
@@ -35,6 +38,7 @@ const MODE_ICONS: Record<RuntimeEnvMode, typeof LaptopIcon> = {
|
||||
cloud: CloudIcon,
|
||||
local: LaptopIcon,
|
||||
none: MonitorOffIcon,
|
||||
sandbox: BoxIcon,
|
||||
};
|
||||
|
||||
const styles = createStaticStyles(({ css }) => ({
|
||||
@@ -63,6 +67,11 @@ const styles = createStaticStyles(({ css }) => ({
|
||||
background: ${cssVar.colorFillSecondary};
|
||||
}
|
||||
`,
|
||||
divider: css`
|
||||
height: 1px;
|
||||
margin-block: 4px;
|
||||
background: ${cssVar.colorBorderSecondary};
|
||||
`,
|
||||
modeDesc: css`
|
||||
font-size: 12px;
|
||||
color: ${cssVar.colorTextTertiary};
|
||||
@@ -107,16 +116,21 @@ const RuntimeConfig = memo(() => {
|
||||
const { updateAgentChatConfig } = useUpdateAgentConfig();
|
||||
const [dirPopoverOpen, setDirPopoverOpen] = useState(false);
|
||||
const [modePopoverOpen, setModePopoverOpen] = useState(false);
|
||||
const [devices, setDevices] = useState<Awaited<ReturnType<typeof deviceService.listDevices>>>([]);
|
||||
const [devicesLoading, setDevicesLoading] = useState(false);
|
||||
const showContextWindow = useChatInputStore((s) =>
|
||||
s.rightActions.flat().includes('contextWindow'),
|
||||
);
|
||||
|
||||
const [isLoading, runtimeMode, isHeterogeneous, enableAgentMode] = useAgentStore((s) => [
|
||||
agentByIdSelectors.isAgentConfigLoadingById(agentId)(s),
|
||||
chatConfigByIdSelectors.getRuntimeModeById(agentId)(s),
|
||||
agentId ? agentByIdSelectors.isAgentHeterogeneousById(agentId)(s) : false,
|
||||
agentByIdSelectors.getAgentEnableModeById(agentId)(s),
|
||||
]);
|
||||
const [isLoading, runtimeMode, isHeterogeneous, enableAgentMode, deviceId] = useAgentStore(
|
||||
(s) => [
|
||||
agentByIdSelectors.isAgentConfigLoadingById(agentId)(s),
|
||||
chatConfigByIdSelectors.getRuntimeModeById(agentId)(s),
|
||||
agentId ? agentByIdSelectors.isAgentHeterogeneousById(agentId)(s) : false,
|
||||
agentByIdSelectors.getAgentEnableModeById(agentId)(s),
|
||||
chatConfigByIdSelectors.getDeviceIdById(agentId)(s),
|
||||
],
|
||||
);
|
||||
|
||||
const topicWorkingDirectory = useChatStore(topicSelectors.currentTopicWorkingDirectory);
|
||||
const agentWorkingDirectory = useAgentStore((s) =>
|
||||
@@ -126,6 +140,17 @@ const RuntimeConfig = memo(() => {
|
||||
|
||||
const repoType = useRepoType(effectiveWorkingDirectory);
|
||||
|
||||
// Fetch device list when popover opens (desktop only)
|
||||
useEffect(() => {
|
||||
if (modePopoverOpen && isDesktop) {
|
||||
setDevicesLoading(true);
|
||||
deviceService.listDevices().then((list) => {
|
||||
setDevices(list);
|
||||
setDevicesLoading(false);
|
||||
});
|
||||
}
|
||||
}, [modePopoverOpen]);
|
||||
|
||||
const dirIconNode = useMemo((): ReactNode => {
|
||||
if (!effectiveWorkingDirectory) return <Icon icon={SquircleDashed} size={14} />;
|
||||
if (repoType === 'github') return <Github size={14} />;
|
||||
@@ -134,18 +159,43 @@ const RuntimeConfig = memo(() => {
|
||||
}, [effectiveWorkingDirectory, repoType]);
|
||||
|
||||
const switchMode = useCallback(
|
||||
async (mode: RuntimeEnvMode) => {
|
||||
if (mode === runtimeMode) return;
|
||||
async (mode: RuntimeEnvMode, opts?: { deviceId?: string }) => {
|
||||
if (mode === runtimeMode && opts?.deviceId === deviceId) return;
|
||||
|
||||
const platform = isDesktop ? 'desktop' : 'web';
|
||||
|
||||
await updateAgentChatConfig({
|
||||
runtimeEnv: { runtimeMode: { [platform]: mode } },
|
||||
runtimeEnv: {
|
||||
deviceId: opts?.deviceId,
|
||||
runtimeMode: { [platform]: mode },
|
||||
},
|
||||
});
|
||||
},
|
||||
[runtimeMode, updateAgentChatConfig],
|
||||
[runtimeMode, deviceId, updateAgentChatConfig],
|
||||
);
|
||||
|
||||
// Compute the display label for the mode button
|
||||
const activeDevice = useMemo(
|
||||
() => (deviceId ? devices.find((d) => d.deviceId === deviceId) : undefined),
|
||||
[deviceId, devices],
|
||||
);
|
||||
|
||||
const ModeIcon = MODE_ICONS[runtimeMode] || LaptopIcon;
|
||||
|
||||
const modeLabel = useMemo(() => {
|
||||
// When running on a specific device, show device hostname
|
||||
if (runtimeMode === 'local' && activeDevice) {
|
||||
return activeDevice.hostname;
|
||||
}
|
||||
return t(`runtimeEnv.mode.${runtimeMode}`);
|
||||
}, [runtimeMode, activeDevice, t]);
|
||||
|
||||
const displayName = effectiveWorkingDirectory
|
||||
? effectiveWorkingDirectory.split('/').findLast(Boolean) || effectiveWorkingDirectory
|
||||
: tPlugin('localSystem.workingDirectory.notSet');
|
||||
|
||||
const hasDevices = devices.length > 0;
|
||||
|
||||
// Skeleton placeholder to prevent layout jump during loading
|
||||
if (!agentId || isLoading) {
|
||||
return (
|
||||
@@ -156,66 +206,93 @@ const RuntimeConfig = memo(() => {
|
||||
);
|
||||
}
|
||||
|
||||
const ModeIcon = MODE_ICONS[runtimeMode];
|
||||
const modeLabel = t(`runtimeEnv.mode.${runtimeMode}`);
|
||||
|
||||
const displayName = effectiveWorkingDirectory
|
||||
? effectiveWorkingDirectory.split('/').findLast(Boolean) || effectiveWorkingDirectory
|
||||
: tPlugin('localSystem.workingDirectory.notSet');
|
||||
|
||||
const modes: { desc: string; icon: typeof LaptopIcon; label: string; mode: RuntimeEnvMode }[] = [
|
||||
// Local mode is desktop-only
|
||||
...(isDesktop
|
||||
? [
|
||||
{
|
||||
desc: t('runtimeEnv.mode.localDesc'),
|
||||
icon: LaptopIcon,
|
||||
label: t('runtimeEnv.mode.local'),
|
||||
mode: 'local' as RuntimeEnvMode,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
desc: t('runtimeEnv.mode.cloudDesc'),
|
||||
icon: CloudIcon,
|
||||
label: t('runtimeEnv.mode.cloud'),
|
||||
mode: 'cloud',
|
||||
},
|
||||
{
|
||||
desc: t('runtimeEnv.mode.noneDesc'),
|
||||
icon: MonitorOffIcon,
|
||||
label: t('runtimeEnv.mode.none'),
|
||||
mode: 'none',
|
||||
},
|
||||
];
|
||||
// ─── Popover Content ───
|
||||
|
||||
const modeContent = (
|
||||
<Flexbox gap={4} style={{ minWidth: 280 }}>
|
||||
{modes.map(({ mode, icon, label, desc }) => (
|
||||
{/* ── Device section (desktop only) ── */}
|
||||
{isDesktop && (
|
||||
<>
|
||||
<SectionHeader label={t('runtimeEnv.section.device')} />
|
||||
{devicesLoading ? (
|
||||
<Flexbox paddingBlock={12} paddingInline={8}>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="small"
|
||||
style={{ height: 16, marginBottom: 4, width: '60%' }}
|
||||
/>
|
||||
<Skeleton.Button active size="small" style={{ height: 12, width: '40%' }} />
|
||||
</Flexbox>
|
||||
) : hasDevices ? (
|
||||
<DeviceSelector
|
||||
activeDeviceId={deviceId}
|
||||
devices={devices}
|
||||
onSelect={(id) => switchMode('local', { deviceId: id })}
|
||||
/>
|
||||
) : (
|
||||
<Flexbox
|
||||
className={styles.modeOptionDesc}
|
||||
paddingBlock={8}
|
||||
paddingInline={8}
|
||||
>
|
||||
{t('runtimeEnv.device.empty')}
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
<div className={styles.divider} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ── Sandbox ── */}
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'flex-start'}
|
||||
gap={12}
|
||||
className={cx(
|
||||
styles.modeOption,
|
||||
(runtimeMode === 'sandbox' || runtimeMode === 'cloud') && styles.modeOptionActive,
|
||||
)}
|
||||
onClick={() => switchMode('sandbox')}
|
||||
>
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'flex-start'}
|
||||
className={cx(styles.modeOption, runtimeMode === mode && styles.modeOptionActive)}
|
||||
gap={12}
|
||||
key={mode}
|
||||
onClick={() => switchMode(mode)}
|
||||
align={'center'}
|
||||
className={styles.modeOptionIcon}
|
||||
flex={'none'}
|
||||
height={32}
|
||||
justify={'center'}
|
||||
width={32}
|
||||
>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={styles.modeOptionIcon}
|
||||
flex={'none'}
|
||||
height={32}
|
||||
justify={'center'}
|
||||
width={32}
|
||||
>
|
||||
<Icon icon={icon} />
|
||||
</Flexbox>
|
||||
<Flexbox flex={1}>
|
||||
<div className={styles.modeOptionTitle}>{label}</div>
|
||||
<div className={styles.modeOptionDesc}>{desc}</div>
|
||||
</Flexbox>
|
||||
<Icon icon={BoxIcon} />
|
||||
</Flexbox>
|
||||
))}
|
||||
<Flexbox flex={1}>
|
||||
<div className={styles.modeOptionTitle}>{t('runtimeEnv.mode.sandbox')}</div>
|
||||
<div className={styles.modeOptionDesc}>{t('runtimeEnv.mode.sandboxDesc')}</div>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
|
||||
{/* ── Disabled ── */}
|
||||
<Flexbox
|
||||
horizontal
|
||||
align={'flex-start'}
|
||||
className={cx(styles.modeOption, runtimeMode === 'none' && styles.modeOptionActive)}
|
||||
gap={12}
|
||||
onClick={() => switchMode('none')}
|
||||
>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={styles.modeOptionIcon}
|
||||
flex={'none'}
|
||||
height={32}
|
||||
justify={'center'}
|
||||
width={32}
|
||||
>
|
||||
<Icon icon={MonitorOffIcon} />
|
||||
</Flexbox>
|
||||
<Flexbox flex={1}>
|
||||
<div className={styles.modeOptionTitle}>{t('runtimeEnv.mode.none')}</div>
|
||||
<div className={styles.modeOptionDesc}>{t('runtimeEnv.mode.noneDesc')}</div>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ export const createServerAgentToolsEngine = (
|
||||
// Always-on builtin tools
|
||||
...Object.fromEntries(alwaysOnToolIds.map((id) => [id, true])),
|
||||
// System-level rules (may override user selection for specific tools)
|
||||
[CloudSandboxManifest.identifier]: runtimeMode === 'cloud',
|
||||
[CloudSandboxManifest.identifier]: runtimeMode === 'cloud' || runtimeMode === 'sandbox',
|
||||
[KnowledgeBaseManifest.identifier]: hasEnabledKnowledgeBases,
|
||||
// Local-system: gated by `canUseDevice` (resolveDeviceAccessPolicy)
|
||||
// first — keeps external bot senders out before runtime checks even
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { type DeviceAttachment } from '@lobechat/builtin-tool-remote-device';
|
||||
|
||||
import { lambdaClient } from '@/libs/trpc/client';
|
||||
|
||||
export const deviceService = {
|
||||
/**
|
||||
* List all online devices bound to the current user.
|
||||
* Returns devices from the device-gateway via tRPC.
|
||||
*/
|
||||
listDevices: async (): Promise<DeviceAttachment[]> => {
|
||||
try {
|
||||
return await lambdaClient.device.listDevices.query();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the user has any online devices.
|
||||
*/
|
||||
getStatus: async (): Promise<{ deviceCount: number; online: boolean }> => {
|
||||
try {
|
||||
return await lambdaClient.device.status.query();
|
||||
} catch {
|
||||
return { deviceCount: 0, online: false };
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -58,17 +58,26 @@ const getRuntimeEnvConfigById = (agentId: string) => (s: AgentStoreState) =>
|
||||
const isLocalSystemEnabledById = (agentId: string) => (s: AgentStoreState) =>
|
||||
getRuntimeModeById(agentId)(s) === 'local';
|
||||
|
||||
/** Get the selected device ID for the agent (desktop only) */
|
||||
const getDeviceIdById =
|
||||
(agentId: string) =>
|
||||
(s: AgentStoreState): string | undefined =>
|
||||
getChatConfigById(agentId)(s).runtimeEnv?.deviceId;
|
||||
|
||||
/**
|
||||
* Get runtime environment mode by agent ID.
|
||||
* Reads from `runtimeMode[platform]`, defaults to 'local' on desktop, 'none' on web.
|
||||
* Legacy 'cloud' values are normalized to 'sandbox' for backward compatibility.
|
||||
*/
|
||||
const getRuntimeModeById =
|
||||
(agentId: string) =>
|
||||
(s: AgentStoreState): RuntimeEnvMode => {
|
||||
const runtimeEnv = getChatConfigById(agentId)(s).runtimeEnv;
|
||||
const platform = isDesktop ? 'desktop' : 'web';
|
||||
const mode = runtimeEnv?.runtimeMode?.[platform] ?? (isDesktop ? 'local' : 'none');
|
||||
|
||||
return runtimeEnv?.runtimeMode?.[platform] ?? (isDesktop ? 'local' : 'none');
|
||||
// Legacy backward compatibility: map 'cloud' to 'sandbox'
|
||||
return mode === 'cloud' ? 'sandbox' : mode;
|
||||
};
|
||||
|
||||
const getSkillActivateModeById =
|
||||
@@ -78,6 +87,7 @@ const getSkillActivateModeById =
|
||||
|
||||
export const chatConfigByIdSelectors = {
|
||||
getChatConfigById,
|
||||
getDeviceIdById,
|
||||
getEnableHistoryCountById,
|
||||
getHistoryCountById,
|
||||
getRuntimeEnvConfigById,
|
||||
|
||||
@@ -34,8 +34,10 @@ const isMemoryToolEnabled = (s: AgentStoreState) =>
|
||||
const isLocalSystemEnabled = (s: AgentStoreState) =>
|
||||
chatConfigByIdSelectors.isLocalSystemEnabledById(s.activeAgentId || '')(s);
|
||||
|
||||
const isCloudSandboxEnabled = (s: AgentStoreState) =>
|
||||
chatConfigByIdSelectors.getRuntimeModeById(s.activeAgentId || '')(s) === 'cloud';
|
||||
const isCloudSandboxEnabled = (s: AgentStoreState) => {
|
||||
const mode = chatConfigByIdSelectors.getRuntimeModeById(s.activeAgentId || '')(s);
|
||||
return mode === 'cloud' || mode === 'sandbox';
|
||||
};
|
||||
|
||||
const skillActivateMode = (s: AgentStoreState) =>
|
||||
chatConfigByIdSelectors.getSkillActivateModeById(s.activeAgentId || '')(s);
|
||||
|
||||
Reference in New Issue
Block a user