* chore: upgrade @opencode-ai/sdk to 1.1.40 * chore(ui): bump @opencode-ai/sdk to 1.1.40 * feat(chat): add mobile controls drawer and panel switching Add mobile-only controls drawer with open/close and panel switching Introduce state and handlers for mobile controls and panels Reset mobile UI state when switching to non-mobile or returning to unified controls * feat: add mobile chat controls utilities Add utilities to compute and display the selected agent and model names in mobile chat controls. Add effort variant formatting, serialization, parsing, and ranking helpers for quick options. Expose helpers to build quick effort option lists for the UI * feat(ModelControls): allow external mobile panel control Initialize mobile panel state from external props when provided Fall back to internal mobile panel state when external control is absent Notify parent on panel changes via onMobilePanelChange callback * feat(chat): add StatusChip component Add StatusChip button that displays agent, model, and effort Show marquee animation when the label is truncated Bind to config and session stores to reflect current context * feat: add UnifiedControlsDrawer chat controls Add a side panel to switch agent, model, and effort quickly Show recent agents and models for faster reselect Persist agent/model/variant selections in session * fix(openchamber): correct base64 to Uint8Array typing * feat: add reduced-motion support for marquee animations Add marquee-text--auto to enable continuous scrolling Introduce prefers-reduced-motion media query to disable animations Apply reduced-motion rules to active marquee and hover states * feat[worktrees]: enhance sdk worktree removal with fallbacks Add dynamic resolution of remove/delete/archive methods for worktrees Fallback to delete or archive when remove is not available Throw clear error when SDK version does not support worktree removal * feat(ui): track recent agents and efforts in UI store Add recentAgents array to UI state for quick access Track up to 5 variants per provider/model in recentEfforts Expose addRecentAgent and addRecentEffort actions to update history * fix(deps): upgrade @opencode-ai/sdk to 1.1.42 Upgrade the OpenCode AI SDK to 1.1.42 across packages Refresh lockfile entries to reflect the new SDK version and integrity hash Ensure downstream packages consume the latest SDK and remain compatible * refactor: simplify agent overflow logic in UnifiedControlsDrawer
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import type { Agent } from '@opencode-ai/sdk/v2';
|
|
|
|
export type MobileControlsPanel = 'model' | 'agent' | 'variant' | null;
|
|
|
|
export const isPrimaryMode = (mode?: string) => mode === 'primary' || mode === 'all' || mode === undefined || mode === null;
|
|
|
|
export const capitalizeLabel = (value: string) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
|
|
export const getAgentDisplayName = (agents: Agent[], agentName?: string) => {
|
|
if (agentName) {
|
|
const agent = agents.find((entry) => entry.name === agentName);
|
|
return agent ? capitalizeLabel(agent.name) : capitalizeLabel(agentName);
|
|
}
|
|
|
|
const primaryAgents = agents.filter((agent) => isPrimaryMode(agent.mode));
|
|
const buildAgent = primaryAgents.find((agent) => agent.name === 'build');
|
|
const fallbackAgent = buildAgent || primaryAgents[0] || agents[0];
|
|
return fallbackAgent ? capitalizeLabel(fallbackAgent.name) : 'Select agent';
|
|
};
|
|
|
|
type ProviderModel = { id?: string; name?: string };
|
|
|
|
export const getModelDisplayName = (
|
|
provider: { models?: ProviderModel[] } | undefined,
|
|
modelId: string | undefined,
|
|
) => {
|
|
if (!provider || !modelId) {
|
|
return 'Not selected';
|
|
}
|
|
const models = Array.isArray(provider.models) ? provider.models : [];
|
|
const model = models.find((entry) => entry.id === modelId);
|
|
if (typeof model?.name === 'string' && model.name.trim().length > 0) {
|
|
return model.name;
|
|
}
|
|
if (typeof model?.id === 'string' && model.id.trim().length > 0) {
|
|
return model.id;
|
|
}
|
|
return modelId;
|
|
};
|
|
|
|
export const formatEffortLabel = (variant?: string) => {
|
|
if (!variant || variant.trim().length === 0) {
|
|
return 'Default';
|
|
}
|
|
const trimmed = variant.trim();
|
|
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
return trimmed;
|
|
}
|
|
return capitalizeLabel(trimmed);
|
|
};
|
|
|
|
export const DEFAULT_EFFORT_KEY = 'default';
|
|
|
|
export const serializeEffortVariant = (variant?: string) => {
|
|
const trimmed = typeof variant === 'string' ? variant.trim() : '';
|
|
return trimmed.length > 0 ? trimmed : DEFAULT_EFFORT_KEY;
|
|
};
|
|
|
|
export const parseEffortVariant = (variant: string) => {
|
|
return variant === DEFAULT_EFFORT_KEY ? undefined : variant;
|
|
};
|
|
|
|
const EFFORT_RANKS: Record<string, number> = {
|
|
max: 6,
|
|
maximum: 6,
|
|
xhigh: 5,
|
|
high: 4,
|
|
medium: 3,
|
|
default: 2,
|
|
low: 1,
|
|
min: 0,
|
|
minimal: 0,
|
|
};
|
|
|
|
export const getEffortRank = (variant?: string) => {
|
|
if (!variant || variant.trim().length === 0) {
|
|
return EFFORT_RANKS.default;
|
|
}
|
|
const normalized = variant.trim().toLowerCase();
|
|
if (Object.prototype.hasOwnProperty.call(EFFORT_RANKS, normalized)) {
|
|
return EFFORT_RANKS[normalized];
|
|
}
|
|
const numeric = Number.parseFloat(normalized);
|
|
return Number.isFinite(numeric) ? numeric : 0;
|
|
};
|
|
|
|
export const getQuickEffortOptions = (variants: string[]) => {
|
|
const options = new Map<string, string | undefined>();
|
|
options.set('default', undefined);
|
|
for (const variant of variants) {
|
|
options.set(variant, variant);
|
|
}
|
|
|
|
const ordered = Array.from(options.values()).sort((a, b) => getEffortRank(b) - getEffortRank(a));
|
|
if (ordered.length <= 4) {
|
|
return ordered;
|
|
}
|
|
|
|
const top = ordered.slice(0, 3);
|
|
const lowest = ordered[ordered.length - 1];
|
|
if (top.some((item) => item === lowest)) {
|
|
return top;
|
|
}
|
|
return [...top, lowest];
|
|
};
|