feat(pro): wire all 7 Pro components into app

- ProjectBox: 5 Pro tabs (Analytics, Budget, Export, Symbols, Agent Mem)
  with PERSISTED-LAZY mount, proStatus() feature gate, peach accent color
- SettingsPanel: Pro tab (Accounts + Marketplace) conditionally shown
- ProSettings.svelte: wrapper with sub-tabs for AccountSwitcher + PluginMarketplace
- Feature detection via dynamic import of pro-bridge + proStatus() call
- All tabs hidden when agor-pro plugin not loaded (community edition)
This commit is contained in:
Hibryda 2026-03-18 02:01:18 +01:00
parent 0953395423
commit d1463d4d1e
4 changed files with 102 additions and 4 deletions

View file

@ -16,6 +16,11 @@
import TestingTab from './TestingTab.svelte';
import MetricsPanel from './MetricsPanel.svelte';
import AuditLogTab from './AuditLogTab.svelte';
import AnalyticsDashboard from '../../commercial/AnalyticsDashboard.svelte';
import SessionExporter from '../../commercial/SessionExporter.svelte';
import BudgetManager from '../../commercial/BudgetManager.svelte';
import ProjectMemory from '../../commercial/ProjectMemory.svelte';
import CodeIntelligence from '../../commercial/CodeIntelligence.svelte';
import {
getTerminalTabs, getActiveGroup,
getFocusFlashProjectId, onProjectTabSwitch, onTerminalToggle,
@ -43,8 +48,14 @@
let mainSessionId = $state<string | null>(null);
let terminalExpanded = $state(false);
type ProjectTab = 'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories' | 'metrics' | 'tasks' | 'architecture' | 'selenium' | 'tests' | 'audit';
type ProjectTab = 'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories' | 'metrics' | 'tasks' | 'architecture' | 'selenium' | 'tests' | 'audit' | 'analytics' | 'export' | 'budget' | 'promemory' | 'symbols';
let activeTab = $state<ProjectTab>('model');
let proAvailable = $state(false);
// Detect Pro edition availability
$effect(() => {
import('../../commercial/pro-bridge').then(m => m.proStatus()).then(() => { proAvailable = true; }).catch(() => {});
});
let activeGroup = $derived(getActiveGroup());
let agentRole = $derived(project.agentRole);
@ -299,6 +310,13 @@
{#if isAgent && agentRole === 'manager'}
<button class="ptab ptab-role" class:active={activeTab === 'audit'} onclick={() => switchTab('audit')}>Audit</button>
{/if}
{#if proAvailable}
<button class="ptab ptab-pro" class:active={activeTab === 'analytics'} onclick={() => switchTab('analytics')}>Analytics</button>
<button class="ptab ptab-pro" class:active={activeTab === 'budget'} onclick={() => switchTab('budget')}>Budget</button>
<button class="ptab ptab-pro" class:active={activeTab === 'export'} onclick={() => switchTab('export')}>Export</button>
<button class="ptab ptab-pro" class:active={activeTab === 'symbols'} onclick={() => switchTab('symbols')}>Symbols</button>
<button class="ptab ptab-pro" class:active={activeTab === 'promemory'} onclick={() => switchTab('promemory')}>Agent Mem</button>
{/if}
</div>
<div class="project-content-area">
@ -362,6 +380,31 @@
<AuditLogTab groupId={activeGroup.id} />
</div>
{/if}
{#if proAvailable && everActivated['analytics']}
<div class="content-pane" style:display={activeTab === 'analytics' ? 'flex' : 'none'}>
<AnalyticsDashboard projectId={project.id} />
</div>
{/if}
{#if proAvailable && everActivated['budget']}
<div class="content-pane" style:display={activeTab === 'budget' ? 'flex' : 'none'}>
<BudgetManager projectId={project.id} />
</div>
{/if}
{#if proAvailable && everActivated['export']}
<div class="content-pane" style:display={activeTab === 'export' ? 'flex' : 'none'}>
<SessionExporter projectId={project.id} sessionId={mainSessionId ?? undefined} />
</div>
{/if}
{#if proAvailable && everActivated['symbols']}
<div class="content-pane" style:display={activeTab === 'symbols' ? 'flex' : 'none'}>
<CodeIntelligence projectPath={project.cwd} />
</div>
{/if}
{#if proAvailable && everActivated['promemory']}
<div class="content-pane" style:display={activeTab === 'promemory' ? 'flex' : 'none'}>
<ProjectMemory projectId={project.id} />
</div>
{/if}
</div>
<div class="terminal-section" style:display={activeTab === 'model' ? 'flex' : 'none'}>
@ -470,6 +513,14 @@
color: var(--ctp-text);
}
.ptab-pro {
color: var(--ctp-peach);
}
.ptab-pro:hover {
color: var(--ctp-text);
}
.project-content-area {
overflow: hidden;
position: relative;

View file

@ -9,13 +9,14 @@
import ProjectSettings from './categories/ProjectSettings.svelte';
import OrchestrationSettings from './categories/OrchestrationSettings.svelte';
import AdvancedSettings from './categories/AdvancedSettings.svelte';
import ProSettings from './categories/ProSettings.svelte';
interface Props {
onClose?: () => void;
}
let { onClose }: Props = $props();
const CATEGORIES: { id: SettingsCategory; label: string; icon: string }[] = [
const BASE_CATEGORIES: { id: SettingsCategory; label: string; icon: string }[] = [
{ id: 'appearance', label: 'Appearance', icon: '🎨' },
{ id: 'agents', label: 'Agents', icon: '🤖' },
{ id: 'security', label: 'Security', icon: '🛡' },
@ -24,6 +25,11 @@
{ id: 'advanced', label: 'Advanced', icon: '⚡' },
];
let proAvailable = $state(false);
let CATEGORIES = $derived(proAvailable
? [...BASE_CATEGORIES, { id: 'pro' as SettingsCategory, label: 'Pro', icon: '💎' }]
: BASE_CATEGORIES);
let activeCategory = $state<SettingsCategory>('appearance');
let searchQuery = $state('');
let searchResults = $derived(searchQuery.length >= 2
@ -78,7 +84,10 @@
}
let searchInput: HTMLInputElement | undefined = $state();
onMount(() => { searchInput?.focus(); });
onMount(() => {
searchInput?.focus();
import('../commercial/pro-bridge').then(m => m.proStatus()).then(() => { proAvailable = true; }).catch(() => {});
});
</script>
<div class="settings-panel" onkeydown={handleSidebarKeydown}>
@ -142,6 +151,8 @@
<OrchestrationSettings />
{:else if activeCategory === 'advanced'}
<AdvancedSettings />
{:else if activeCategory === 'pro'}
<ProSettings />
{/if}
</div>
</div>

View file

@ -0,0 +1,35 @@
<script lang="ts">
import AccountSwitcher from '../../commercial/AccountSwitcher.svelte';
import PluginMarketplace from '../../commercial/PluginMarketplace.svelte';
let activeSection = $state<'accounts' | 'marketplace'>('accounts');
</script>
<div class="pro-settings">
<div class="pro-tabs">
<button class="pro-tab" class:active={activeSection === 'accounts'} onclick={() => activeSection = 'accounts'}>
Accounts
</button>
<button class="pro-tab" class:active={activeSection === 'marketplace'} onclick={() => activeSection = 'marketplace'}>
Marketplace
</button>
</div>
<div class="pro-content">
{#if activeSection === 'accounts'}
<AccountSwitcher />
{:else}
<PluginMarketplace />
{/if}
</div>
</div>
<style>
.pro-settings { display: flex; flex-direction: column; gap: 0.5rem; }
.pro-tabs { display: flex; gap: 0.25rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--ctp-surface0); }
.pro-tab { padding: 0.3rem 0.75rem; background: none; border: none; border-radius: 0.25rem;
color: var(--ctp-subtext0); cursor: pointer; font-size: 0.8rem; }
.pro-tab:hover { background: var(--ctp-surface0); color: var(--ctp-text); }
.pro-tab.active { background: color-mix(in srgb, var(--ctp-peach) 15%, transparent); color: var(--ctp-peach); }
.pro-content { flex: 1; overflow-y: auto; }
</style>

View file

@ -9,7 +9,8 @@ export type SettingsCategory =
| 'security'
| 'projects'
| 'orchestration'
| 'advanced';
| 'advanced'
| 'pro';
export interface SettingEntry {
key: string;