Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions DEMO_SCRIPT.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Use this exact sequence when time is short. It maps directly to product value:
- Explain that the app chooses the smallest viable model for the workload, not the largest available one

3. Open the extension on a real page.
- On a fresh unlocked install, run `Try local example` once without selecting page text
- Show the real local Readable and Structured result, then the Markdown and JSON exports
- Reload once to show that the first-run prompt stays resolved
- Continue with the ordinary selected-text path
- Highlight a paragraph
- Open the side panel
- Click `Extract JSON`
Expand Down
22 changes: 22 additions & 0 deletions background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { log, error } from '../utils/logger.js';
import { requireFeature, getLicenseTier, attachLicenseToken, refreshLicense } from './tier-service.js';
import { getEntitlementSnapshot } from './entitlement-service.js';
import { ApiRequestError } from '../api/request.js';
import { FIRST_RUN_EXAMPLE } from '../shared/first-run-example.js';
const MEMORY_ENABLED_KEY = 'selectpilot_memory_enabled_v1';
const MEMORY_LEDGER_KEY = 'selectpilot_memory_ledger_v1';
async function canUseProjectMemory() {
Expand Down Expand Up @@ -190,6 +191,23 @@ async function handleExtract(preset) {
});
return result;
}
async function handleFirstRunExtract() {
const entitlement = await getEntitlementSnapshot();
const { allowed } = await requireFeature('structured_extraction');
if (!entitlement?.token || !allowed) {
throw new Error('Paid license required for deterministic extraction');
}
return extract({
text: FIRST_RUN_EXAMPLE.text,
preset: FIRST_RUN_EXAMPLE.preset,
url: FIRST_RUN_EXAMPLE.url,
title: FIRST_RUN_EXAMPLE.title,
metadata: {
source: 'selectpilot_first_run',
sample_version: FIRST_RUN_EXAMPLE.version,
},
});
}
async function handleTranscribe() {
const { allowed } = await requireFeature('audio_transcription');
if (!allowed)
Expand Down Expand Up @@ -268,6 +286,10 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
sendResponse(await handleExtract(msg.preset));
return;
}
if (msg.type === 'panel:extract_demo') {
sendResponse(await handleFirstRunExtract());
return;
}
if (msg.type === 'panel:vision') {
sendResponse(await handleVision());
return;
Expand Down
23 changes: 23 additions & 0 deletions background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { requireFeature, getLicenseTier, attachLicenseToken, refreshLicense } fr
import { getEntitlementSnapshot } from './entitlement-service.js';
import { ApiRequestError } from '../api/request.js';
import type { AgentContext } from '../agent/agent-types.js';
import { FIRST_RUN_EXAMPLE } from '../shared/first-run-example.js';

type MemoryEntry = {
action: 'extract' | 'summarize' | 'agent';
Expand Down Expand Up @@ -218,6 +219,24 @@ async function handleExtract(preset?: string): Promise<any> {
return result;
}

async function handleFirstRunExtract(): Promise<any> {
const entitlement = await getEntitlementSnapshot();
const { allowed } = await requireFeature('structured_extraction');
if (!entitlement?.token || !allowed) {
throw new Error('Paid license required for deterministic extraction');
}
return extract({
text: FIRST_RUN_EXAMPLE.text,
preset: FIRST_RUN_EXAMPLE.preset,
url: FIRST_RUN_EXAMPLE.url,
title: FIRST_RUN_EXAMPLE.title,
metadata: {
source: 'selectpilot_first_run',
sample_version: FIRST_RUN_EXAMPLE.version,
},
});
}

async function handleTranscribe(): Promise<any> {
const { allowed } = await requireFeature('audio_transcription');
if (!allowed) throw new Error('Feature blocked: upgrade tier for audio transcription');
Expand Down Expand Up @@ -296,6 +315,10 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
sendResponse(await handleExtract(msg.preset));
return;
}
if (msg.type === 'panel:extract_demo') {
sendResponse(await handleFirstRunExtract());
return;
}
if (msg.type === 'panel:vision') {
sendResponse(await handleVision());
return;
Expand Down
18 changes: 18 additions & 0 deletions panel/panel.css
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,24 @@ html, body {
gap: 8px;
}

.selection-example-action {
min-height: 44px;
margin-top: 4px;
padding: 10px 14px;
border: 1px solid var(--accent);
border-radius: 14px;
background: var(--accent);
color: white;
font: inherit;
font-weight: 600;
cursor: pointer;
}

.selection-example-action:disabled {
opacity: 0.5;
cursor: progress;
}

.intent-shell {
border: 1px solid var(--panel-border);
border-radius: 18px;
Expand Down
59 changes: 55 additions & 4 deletions panel/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { buildKnowledgePackage } from './knowledge-connectors.js';
import { applyRuntimeEvent, setIntent, setSelectionContext, setVisiblePanels } from './state/runtimeStore.js';
import { loadBottleneckReport, loadDeterminismReport, loadFrontierReport } from './state/reportStore.js';
import { getTopologyForComponent, validateTopologyMap } from './layout/topologyMap.js';
import { FIRST_RUN_EXAMPLE } from '../shared/first-run-example.js';
const workflow = $('#workflow');
const exportsEl = $('#exports');
const runtimeStateEl = $('#runtime-state');
Expand Down Expand Up @@ -58,6 +59,7 @@ const runtimeMetaTraceEl = $('#runtime-meta-trace');
const runtimeMetaEventsEl = $('#runtime-meta-events');
const actionButtons = Array.from(document.querySelectorAll('.primary-action, .secondary-grid button, .advanced-grid button'));
const ENTITLEMENT_FRESH_MS = 15 * 60 * 1000;
const FIRST_RUN_COMPLETED_KEY = 'selectpilot_first_run_completed_v1';
let isBusy = false;
let runtimeSnapshot = {
ok: false,
Expand Down Expand Up @@ -92,6 +94,7 @@ let memorySnapshot = {
lastUpdatedAt: null,
};
let entitlementSnapshot = null;
let firstRunCompleted = false;
const BENCHMARK_CACHE_KEY = 'selectpilot_runtime_benchmark_v1';
const RUNTIME_META_MAX_EVENTS = 6;
let runtimeMetaEventSource = null;
Expand Down Expand Up @@ -764,6 +767,7 @@ async function refreshEntitlementStatus() {
entitlementSnapshot = null;
}
renderEntitlementStatus();
renderSelectionState();
syncControlAvailability();
}
async function doSyncOrderToken() {
Expand Down Expand Up @@ -855,6 +859,9 @@ function syncControlAvailability() {
}
if (intentClearButtonEl)
intentClearButtonEl.disabled = isBusy;
const firstRunButton = document.querySelector('#btn-first-run-example');
if (firstRunButton)
firstRunButton.disabled = isBusy || !runtimeReady || !entitlementSnapshot?.token;
}
function populatePresetOptions() {
if (!extractPresetEl)
Expand Down Expand Up @@ -1047,14 +1054,23 @@ function renderSelectionState() {
clearNode(selectionCardEl);
if (!selectionCardEl)
return;
const showFirstRunExample = runtimeSnapshot.ok
&& Boolean(entitlementSnapshot?.token)
&& !selectionPreview.hasSelection
&& !firstRunCompleted;
const header = document.createElement('div');
header.className = 'output-eyebrow';
header.textContent = selectionPreview.hasSelection ? 'Active selection' : 'No active selection';
header.textContent = showFirstRunExample
? 'First result'
: (selectionPreview.hasSelection ? 'Active selection' : 'No active selection');
const title = document.createElement('h3');
title.textContent = selectionPreview.title || 'Current page';
title.textContent = showFirstRunExample ? 'See SelectPilot once' : (selectionPreview.title || 'Current page');
const copy = document.createElement('p');
copy.className = 'selection-copy';
if (selectionPreview.hasSelection) {
if (showFirstRunExample) {
copy.textContent = FIRST_RUN_EXAMPLE.text;
}
else if (selectionPreview.hasSelection) {
copy.textContent = shorten(selectionPreview.selection, 260);
}
else if (selectionPreview.pageText) {
Expand All @@ -1066,8 +1082,19 @@ function renderSelectionState() {
const meta = document.createElement('p');
meta.className = 'selection-copy';
const charCount = selectionPreview.hasSelection ? selectionPreview.selection.length : selectionPreview.pageText.length;
meta.textContent = `${selectionPreview.hasSelection ? 'Selection' : 'Page context'} · ${charCount} chars${selectionPreview.url ? ` · ${selectionPreview.url}` : ''}`;
meta.textContent = showFirstRunExample
? 'Local example · Action Brief · no page content used'
: `${selectionPreview.hasSelection ? 'Selection' : 'Page context'} · ${charCount} chars${selectionPreview.url ? ` · ${selectionPreview.url}` : ''}`;
selectionCardEl.append(header, title, copy, meta);
if (showFirstRunExample) {
const action = document.createElement('button');
action.id = 'btn-first-run-example';
action.className = 'selection-example-action';
action.type = 'button';
action.textContent = 'Try local example';
action.disabled = isBusy;
selectionCardEl.append(action);
}
}
async function refreshSelectionPreview() {
const preview = await request('panel:get_selection_preview');
Expand Down Expand Up @@ -1184,6 +1211,7 @@ async function refreshRuntime() {
setStatus(runtimeSnapshot.error || 'Ollama health check failed');
renderRuntimeState();
}
renderSelectionState();
syncControlAvailability();
}
async function doSummarize() {
Expand Down Expand Up @@ -1232,6 +1260,23 @@ async function doExtract(presetKey) {
renderExports({ markdown: res.markdown, json: res.json, basename: `selectpilot-${selectedPreset.key}` });
setStatus('Done');
}
async function doFirstRunExample() {
const selectedPreset = getExtractionPreset(FIRST_RUN_EXAMPLE.preset);
const res = await request('panel:extract_demo');
renderOutput({
title: res.label || selectedPreset.label,
markdown: res.markdown || '',
json: res.json || {},
eyebrow: 'Your first structured result',
meta: 'Created locally from the example above. Ready to read or export.',
exportBase: 'selectpilot-first-result',
});
renderExports({ markdown: res.markdown, json: res.json, basename: 'selectpilot-first-result' });
await setJSON(FIRST_RUN_COMPLETED_KEY, true);
firstRunCompleted = true;
renderSelectionState();
setStatus('Ready to export');
}
async function doRewrite() {
const prompt = agentPromptEl?.value.trim() || 'Rewrite the selected text in clearer, tighter language.';
setStatus('Rewriting...');
Expand Down Expand Up @@ -1387,6 +1432,11 @@ function bindActions() {
void Promise.all([refreshRuntime(), refreshSelectionPreview(), refreshMemoryStatus(), refreshEntitlementStatus()]);
});
$('#btn-extract')?.addEventListener('click', wrap(() => doExtract()));
selectionCardEl?.addEventListener('click', (event) => {
const target = event.target;
if (target?.id === 'btn-first-run-example')
void wrap(() => doFirstRunExample())();
});
$('#btn-summarize')?.addEventListener('click', wrap(() => doSummarize()));
$('#btn-rewrite')?.addEventListener('click', wrap(() => doRewrite()));
$('#btn-actions')?.addEventListener('click', wrap(() => doActions()));
Expand Down Expand Up @@ -1484,6 +1534,7 @@ async function initialize() {
setStatus(`Topology contract failed: ${[...topologyValidation.errors, ...topologyBindingErrors].join(', ')}`);
}
setVisiblePanels(['selection_surface', 'runtime_surface', 'report_surface']);
firstRunCompleted = Boolean(await getJSON(FIRST_RUN_COMPLETED_KEY));
renderRuntimeMetaOverlay();
void connectRuntimeMetaStream();
refreshIntentSuggestions();
Expand Down
Loading