feat: refactor HomePage to integrate agents store and update related components
feat: add runtime event handling for providers in ProvidersSection feat: update routing to include Channels and Agents pages feat: extend route types and navigation items for Channels and Agents feat: implement agents store for managing agent data and interactions fix: update chat store to utilize agents store for agent-related functionality chore: export agents store from index fix: enhance runtime types for better event handling fix: update Vite config to handle dev server URL correctly
This commit is contained in:
357
src/pages/Agents/components/AgentSettingsDialog.tsx
Normal file
357
src/pages/Agents/components/AgentSettingsDialog.tsx
Normal file
@@ -0,0 +1,357 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import type { AgentSummary } from '@runtime/lib/agents';
|
||||
import type { ProviderAccount } from '@runtime/lib/providers';
|
||||
import AgentsDialogSurface from './AgentsDialogSurface';
|
||||
|
||||
const FIELD_CLASS_NAME = [
|
||||
'w-full rounded-[20px] border border-black/10 bg-[#F8F4EC] px-5 py-4 text-[16px] text-[#171717]',
|
||||
'outline-none transition-colors placeholder:text-[#99A0AE] focus:border-black/20',
|
||||
'disabled:cursor-not-allowed disabled:opacity-65 dark:border-white/10 dark:bg-[#222225] dark:text-[#f3f4f6] dark:placeholder:text-gray-500 dark:focus:border-white/20',
|
||||
].join(' ');
|
||||
|
||||
type AgentSettingsDialogCopy = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
identityTitle: string;
|
||||
nameLabel: string;
|
||||
agentIdLabel: string;
|
||||
workspaceTitle: string;
|
||||
workspaceDescription: string;
|
||||
workspaceLabel: string;
|
||||
inheritedWorkspaceLabel: string;
|
||||
inheritedWorkspaceYes: string;
|
||||
inheritedWorkspaceNo: string;
|
||||
modelTitle: string;
|
||||
providerAccountLabel: string;
|
||||
useDefaultProvider: string;
|
||||
modelRefLabel: string;
|
||||
modelRefPlaceholder: string;
|
||||
effectiveProviderLabel: string;
|
||||
effectiveModelLabel: string;
|
||||
modelHelp: string;
|
||||
managedFromModels: string;
|
||||
openModelsLabel: string;
|
||||
channelsTitle: string;
|
||||
channelSummaryLabel: string;
|
||||
accountBindingsLabel: string;
|
||||
noChannels: string;
|
||||
openChannelsLabel: string;
|
||||
providerLoadErrorPrefix: string;
|
||||
cancelLabel: string;
|
||||
saveLabel: string;
|
||||
savingLabel: string;
|
||||
deleteLabel: string;
|
||||
};
|
||||
|
||||
type AgentSettingsDialogProps = {
|
||||
open: boolean;
|
||||
agent: AgentSummary | null;
|
||||
providerAccounts: ProviderAccount[];
|
||||
providerLoading: boolean;
|
||||
providerError: string | null;
|
||||
defaultProviderAccountId: string | null;
|
||||
defaultModelRef: string | null;
|
||||
mainWorkspacePath: string | null;
|
||||
channelSummary: string;
|
||||
accountBindingCount: number;
|
||||
saving: boolean;
|
||||
deleting: boolean;
|
||||
copy: AgentSettingsDialogCopy;
|
||||
onClose: () => void;
|
||||
onSave: (input: { name: string; providerAccountId: string | null; modelRef: string | null }) => Promise<void> | void;
|
||||
onDelete: (agent: AgentSummary) => Promise<void> | void;
|
||||
onOpenChannels: () => void;
|
||||
onOpenModels: () => void;
|
||||
};
|
||||
|
||||
export default function AgentSettingsDialog({
|
||||
open,
|
||||
agent,
|
||||
providerAccounts,
|
||||
providerLoading,
|
||||
providerError,
|
||||
defaultProviderAccountId,
|
||||
defaultModelRef,
|
||||
mainWorkspacePath,
|
||||
channelSummary,
|
||||
accountBindingCount,
|
||||
saving,
|
||||
deleting,
|
||||
copy,
|
||||
onClose,
|
||||
onSave,
|
||||
onDelete,
|
||||
onOpenChannels,
|
||||
onOpenModels,
|
||||
}: AgentSettingsDialogProps) {
|
||||
const [name, setName] = useState('');
|
||||
const [providerAccountId, setProviderAccountId] = useState('');
|
||||
const [modelRef, setModelRef] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!agent || !open) return;
|
||||
setName(agent.name);
|
||||
setProviderAccountId(agent.providerAccountId ?? '');
|
||||
setModelRef(agent.overrideModelRef ?? '');
|
||||
}, [agent, open]);
|
||||
|
||||
const selectedProvider = useMemo(
|
||||
() => providerAccounts.find((account) => account.id === providerAccountId) ?? null,
|
||||
[providerAccountId, providerAccounts],
|
||||
);
|
||||
|
||||
const defaultProvider = useMemo(
|
||||
() => providerAccounts.find((account) => account.id === defaultProviderAccountId) ?? null,
|
||||
[defaultProviderAccountId, providerAccounts],
|
||||
);
|
||||
|
||||
if (!agent) return null;
|
||||
|
||||
const isDefault = agent.isDefault;
|
||||
const inheritedWorkspace = Boolean(
|
||||
!isDefault
|
||||
&& mainWorkspacePath
|
||||
&& agent.workspace
|
||||
&& agent.workspace === mainWorkspacePath,
|
||||
);
|
||||
const effectiveProviderLabel = selectedProvider?.label || defaultProvider?.label || copy.useDefaultProvider;
|
||||
const effectiveModelLabel = modelRef.trim()
|
||||
|| selectedProvider?.model
|
||||
|| defaultModelRef
|
||||
|| copy.modelRefPlaceholder;
|
||||
const hasChanges = !isDefault && (
|
||||
name.trim() !== agent.name
|
||||
|| providerAccountId !== (agent.providerAccountId ?? '')
|
||||
|| modelRef !== (agent.overrideModelRef ?? '')
|
||||
);
|
||||
|
||||
return (
|
||||
<AgentsDialogSurface
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
title={copy.title}
|
||||
subtitle={copy.subtitle}
|
||||
widthClassName="max-w-[980px]"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="grid gap-5 lg:grid-cols-[1.02fr_0.98fr]">
|
||||
<section className="rounded-[28px] bg-[#F7F2E9] p-6 dark:bg-[#17171a]">
|
||||
<div className="text-[20px] font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.identityTitle}
|
||||
</div>
|
||||
<div className="mt-5 space-y-4">
|
||||
<label className="block">
|
||||
<span className="mb-3 block text-[16px] font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.nameLabel}
|
||||
</span>
|
||||
<input
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
disabled={isDefault || saving || deleting}
|
||||
className={FIELD_CLASS_NAME}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="rounded-[22px] border border-dashed border-black/10 bg-white/70 px-5 py-4 text-[14px] text-[#525866] dark:border-white/10 dark:bg-[#222225] dark:text-gray-300">
|
||||
<div className="font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.agentIdLabel}
|
||||
</div>
|
||||
<div className="mt-2 break-all font-mono text-[13px]">
|
||||
{agent.id}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[22px] border border-dashed border-black/10 bg-white/70 px-5 py-4 dark:border-white/10 dark:bg-[#222225]">
|
||||
<div className="font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.workspaceTitle}
|
||||
</div>
|
||||
<div className="mt-2 text-[14px] leading-[1.6] text-[#5B6475] dark:text-gray-400">
|
||||
{copy.workspaceDescription}
|
||||
</div>
|
||||
<div className="mt-4 grid gap-4 sm:grid-cols-[1fr_auto]">
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#99A0AE] dark:text-gray-500">
|
||||
{copy.workspaceLabel}
|
||||
</div>
|
||||
<div className="mt-2 break-all font-mono text-[13px] text-[#5B6475] dark:text-gray-300">
|
||||
{agent.workspace || '--'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-[148px]">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#99A0AE] dark:text-gray-500">
|
||||
{copy.inheritedWorkspaceLabel}
|
||||
</div>
|
||||
<div className="mt-2 text-[14px] text-[#5B6475] dark:text-gray-300">
|
||||
{inheritedWorkspace ? copy.inheritedWorkspaceYes : copy.inheritedWorkspaceNo}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-[28px] bg-[#F7F2E9] p-6 dark:bg-[#17171a]">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-[20px] font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.channelsTitle}
|
||||
</div>
|
||||
<div className="mt-2 text-[14px] leading-[1.6] text-[#646C7A] dark:text-gray-400">
|
||||
{copy.channelSummaryLabel}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-black/12 bg-white/80 px-5 py-2.5 text-[14px] font-medium text-[#2E3445] transition-colors hover:bg-white dark:border-white/10 dark:bg-[#222225] dark:text-gray-200 dark:hover:bg-[#2a2a2d]"
|
||||
onClick={onOpenChannels}
|
||||
>
|
||||
{copy.openChannelsLabel}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 space-y-4 text-[14px] leading-[1.6] text-[#5B6475] dark:text-gray-300">
|
||||
<div className="rounded-[22px] border border-dashed border-black/10 bg-white/70 px-5 py-4 dark:border-white/10 dark:bg-[#222225]">
|
||||
<div className="font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.channelSummaryLabel}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
{channelSummary || copy.noChannels}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-[22px] border border-dashed border-black/10 bg-white/70 px-5 py-4 dark:border-white/10 dark:bg-[#222225]">
|
||||
<div className="font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.accountBindingsLabel}
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
{accountBindingCount}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="rounded-[28px] bg-[#F7F2E9] p-6 dark:bg-[#17171a]">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-[20px] font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.modelTitle}
|
||||
</div>
|
||||
<div className="mt-2 text-[14px] leading-[1.6] text-[#646C7A] dark:text-gray-400">
|
||||
{isDefault ? copy.managedFromModels : copy.modelHelp}
|
||||
</div>
|
||||
</div>
|
||||
{isDefault ? (
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-black/12 bg-white/80 px-5 py-2.5 text-[14px] font-medium text-[#2E3445] transition-colors hover:bg-white dark:border-white/10 dark:bg-[#222225] dark:text-gray-200 dark:hover:bg-[#2a2a2d]"
|
||||
onClick={onOpenModels}
|
||||
>
|
||||
{copy.openModelsLabel}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isDefault ? null : (
|
||||
<div className="mt-5 grid gap-4 lg:grid-cols-[1fr_1fr_0.95fr]">
|
||||
<label className="block">
|
||||
<span className="mb-3 block text-[16px] font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.providerAccountLabel}
|
||||
</span>
|
||||
<select
|
||||
value={providerAccountId}
|
||||
onChange={(event) => setProviderAccountId(event.target.value)}
|
||||
disabled={providerLoading || saving || deleting}
|
||||
className={FIELD_CLASS_NAME}
|
||||
>
|
||||
<option value="">{copy.useDefaultProvider}</option>
|
||||
{providerAccounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{`${account.label}${account.isDefault ? ' · Default' : ''}${account.model ? ` · ${account.model}` : ''}`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="block">
|
||||
<span className="mb-3 block text-[16px] font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.modelRefLabel}
|
||||
</span>
|
||||
<input
|
||||
value={modelRef}
|
||||
onChange={(event) => setModelRef(event.target.value)}
|
||||
disabled={saving || deleting}
|
||||
placeholder={copy.modelRefPlaceholder}
|
||||
className={FIELD_CLASS_NAME}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="rounded-[22px] border border-dashed border-black/10 bg-white/70 px-5 py-4 text-[14px] text-[#5B6475] dark:border-white/10 dark:bg-[#222225] dark:text-gray-300">
|
||||
<div>
|
||||
<span className="font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.effectiveProviderLabel}
|
||||
</span>
|
||||
<span className="ml-2">{effectiveProviderLabel}</span>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<span className="font-semibold text-[#2E3445] dark:text-[#f3f4f6]">
|
||||
{copy.effectiveModelLabel}
|
||||
</span>
|
||||
<span className="ml-2 break-all font-mono text-[13px]">
|
||||
{effectiveModelLabel}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{providerError && !isDefault ? (
|
||||
<div className="mt-4 rounded-[20px] border border-amber-500/25 bg-amber-500/10 px-4 py-3 text-[13px] text-amber-700 dark:text-amber-300">
|
||||
{`${copy.providerLoadErrorPrefix}${providerError}`}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-end gap-4 pt-2">
|
||||
{!isDefault ? (
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-red-500/20 bg-red-500/10 px-8 py-3 text-[16px] font-medium text-red-600 transition-colors hover:bg-red-500/15 disabled:cursor-not-allowed disabled:opacity-60 dark:text-red-300"
|
||||
disabled={saving || deleting}
|
||||
onClick={() => {
|
||||
void onDelete(agent);
|
||||
}}
|
||||
>
|
||||
{deleting ? copy.savingLabel : copy.deleteLabel}
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-black/12 bg-white/80 px-8 py-3 text-[16px] font-medium text-[#2E3445] transition-colors hover:bg-white dark:border-white/10 dark:bg-[#222225] dark:text-gray-200 dark:hover:bg-[#2a2a2d]"
|
||||
onClick={onClose}
|
||||
disabled={saving || deleting}
|
||||
>
|
||||
{copy.cancelLabel}
|
||||
</button>
|
||||
|
||||
{!isDefault ? (
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full bg-[#7E9DF5] px-8 py-3 text-[16px] font-medium text-white transition-colors hover:bg-[#6E90F3] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={!hasChanges || saving || deleting || name.trim().length === 0}
|
||||
onClick={() => {
|
||||
void onSave({
|
||||
name: name.trim(),
|
||||
providerAccountId: providerAccountId || null,
|
||||
modelRef: modelRef.trim() || null,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{saving ? copy.savingLabel : copy.saveLabel}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</AgentsDialogSurface>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user