mirror of
https://github.com/katanemo/plano.git
synced 2026-06-17 15:25:17 +02:00
stashing changes as checkpoint
This commit is contained in:
parent
76151c2b8d
commit
23d0574a7c
1 changed files with 151 additions and 34 deletions
|
|
@ -2,16 +2,73 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
// --- Mocked lucide-react icons as SVG components ---
|
||||
const Trash2 = ({ className }) => ( <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M3 6h18" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><line x1="10" y1="11" x2="10" y2="17" /><line x1="14" y1="11" x2="14" y2="17" /></svg> );
|
||||
const PlusCircle = ({ className }) => ( <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="16" /><line x1="8" y1="12" x2="16" y2="12" /></svg> );
|
||||
const Trash2 = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
|
||||
<path d="M3 6h18" />
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||
<line x1="10" y1="11" x2="10" y2="17" />
|
||||
<line x1="14" y1="11" x2="14" y2="17" />
|
||||
</svg>
|
||||
);
|
||||
const PlusCircle = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" y1="8" x2="12" y2="16" />
|
||||
<line x1="8" y1="12" x2="16" y2="12" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// --- Mocked UI Components ---
|
||||
const Card = ({ children, className = '' }) => (<div className={`bg-white border border-gray-200 rounded-lg shadow-sm ${className}`}>{children}</div>);
|
||||
const CardContent = ({ children, className = '' }) => (<div className={`p-4 ${className}`}>{children}</div>);
|
||||
const Input = (props) => (<input {...props} className={`w-full px-3 py-2 text-sm text-gray-800 bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${props.className || ''}`} />);
|
||||
const Button = ({ children, variant = 'default', size = 'default', className = '', ...props }) => { const baseClasses = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'; const variantClasses = { default: 'bg-gray-900 text-white hover:bg-gray-800 focus:ring-gray-900', outline: 'border border-gray-300 bg-transparent hover:bg-gray-100 focus:ring-gray-400', ghost: 'hover:bg-gray-100 hover:text-gray-900 focus:ring-gray-400' }; const sizeClasses = { default: 'h-9 px-3', icon: 'h-9 w-9' }; return (<button {...props} className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}>{children}</button>); };
|
||||
const Switch = ({ checked, onCheckedChange, id }) => (<button type="button" role="switch" aria-checked={checked} onClick={() => onCheckedChange(!checked)} id={id} className={`relative inline-flex items-center h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${checked ? 'bg-gray-900' : 'bg-gray-300'}`}><span aria-hidden="true" className={`inline-block h-5 w-5 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out ${checked ? 'translate-x-5' : 'translate-x-0'}`} /></button>);
|
||||
const Label = (props) => (<label {...props} className={`text-sm font-medium leading-none text-gray-700 ${props.className || ''}`} />);
|
||||
const Card = ({ children, className = '' }) => (
|
||||
<div className={`bg-white border border-gray-200 rounded-lg shadow-sm ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
const CardContent = ({ children, className = '' }) => (
|
||||
<div className={`p-4 ${className}`}>{children}</div>
|
||||
);
|
||||
const Input = (props) => (
|
||||
<input
|
||||
{...props}
|
||||
className={`w-full px-3 py-2 text-sm text-gray-800 bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${props.className || ''}`}
|
||||
/>
|
||||
);
|
||||
const Button = ({ children, variant = 'default', size = 'default', className = '', ...props }) => {
|
||||
const baseClasses = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
const variantClasses = {
|
||||
default: 'bg-gray-900 text-white hover:bg-gray-800 focus:ring-gray-900',
|
||||
outline: 'border border-gray-300 bg-transparent hover:bg-gray-100 focus:ring-gray-400',
|
||||
ghost: 'hover:bg-gray-100 hover:text-gray-900 focus:ring-gray-400'
|
||||
};
|
||||
const sizeClasses = { default: 'h-9 px-3', icon: 'h-9 w-9' };
|
||||
return (
|
||||
<button {...props} className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
const Switch = ({ checked, onCheckedChange, id }) => (
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
onClick={() => onCheckedChange(!checked)}
|
||||
id={id}
|
||||
className={`relative inline-flex items-center h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
|
||||
checked ? 'bg-gray-900' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out ${
|
||||
checked ? 'translate-x-5' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
const Label = (props) => (
|
||||
<label {...props} className={`text-sm font-medium leading-none text-gray-700 ${props.className || ''}`} />
|
||||
);
|
||||
|
||||
export default function PreferenceBasedModelSelector() {
|
||||
const [routingEnabled, setRoutingEnabled] = useState(false);
|
||||
|
|
@ -19,39 +76,56 @@ export default function PreferenceBasedModelSelector() {
|
|||
const [defaultModel, setDefaultModel] = useState("gpt-4");
|
||||
const modelOptions = ["gpt-3.5-turbo", "gpt-4", "gpt-4o"];
|
||||
|
||||
// Load settings from chrome storage when the component mounts
|
||||
// Load settings from chrome.storage when the component mounts
|
||||
useEffect(() => {
|
||||
if (chrome.storage) {
|
||||
chrome.storage.sync.get(['routingEnabled', 'preferences', 'defaultModel'], (result) => {
|
||||
if (result.routingEnabled !== undefined) setRoutingEnabled(result.routingEnabled);
|
||||
if (result.preferences) setPreferences(result.preferences);
|
||||
if (result.defaultModel) setDefaultModel(result.defaultModel);
|
||||
});
|
||||
chrome.storage.sync.get(['routingEnabled', 'preferences', 'defaultModel'], (result) => {
|
||||
if (result.routingEnabled !== undefined) setRoutingEnabled(result.routingEnabled);
|
||||
if (result.preferences) setPreferences(result.preferences);
|
||||
if (result.defaultModel) setDefaultModel(result.defaultModel);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updatePreference = (id, key, value) => { setPreferences((prev) => prev.map((p) => (p.id === id ? { ...p, [key]: value } : p))); };
|
||||
const addPreference = () => { const newId = (preferences.length > 0 ? Math.max(...preferences.map(p => p.id)) : 0) + 1; setPreferences([...preferences, { id: newId, naturalLanguage: "", model: defaultModel }]); };
|
||||
const removePreference = (id) => { if (preferences.length > 1) { setPreferences(preferences.filter((p) => p.id !== id)); } };
|
||||
const updatePreference = (id, key, value) => {
|
||||
setPreferences((prev) =>
|
||||
prev.map((p) => (p.id === id ? { ...p, [key]: value } : p))
|
||||
);
|
||||
};
|
||||
const addPreference = () => {
|
||||
const newId =
|
||||
preferences.length > 0 ? Math.max(...preferences.map((p) => p.id)) + 1 : 1;
|
||||
setPreferences([...preferences, { id: newId, naturalLanguage: "", model: defaultModel }]);
|
||||
};
|
||||
const removePreference = (id) => {
|
||||
if (preferences.length > 1) {
|
||||
setPreferences(preferences.filter((p) => p.id !== id));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const settings = { routingEnabled, preferences, defaultModel };
|
||||
// Save to chrome storage
|
||||
// Save to chrome.storage
|
||||
if (chrome.storage) {
|
||||
chrome.storage.sync.set(settings, () => {
|
||||
console.log("Settings saved.");
|
||||
});
|
||||
chrome.storage.sync.set(settings, () => {
|
||||
console.log("[PBMS] Settings saved.");
|
||||
});
|
||||
}
|
||||
|
||||
// Send a message to the active tab's content script
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
// TODO: In a real app, you wouldn't apply preference-based routing here.
|
||||
// This example applies the default model for simplicity.
|
||||
const modelToApply = defaultModel; // Add logic for preference routing if needed
|
||||
chrome.tabs.sendMessage(tabs[0].id, { action: "applyModelSelection", model: modelToApply });
|
||||
const modelToApply = defaultModel; // Replace with your routing logic
|
||||
chrome.tabs.sendMessage(tabs[0].id, { action: "applyModelSelection", model: modelToApply });
|
||||
});
|
||||
|
||||
window.close(); // Close the popup after saving
|
||||
// Close the injected modal by messaging the parent page
|
||||
console.log("[PBMS] Save clicked → closing modal");
|
||||
window.parent.postMessage({ action: "CLOSE_PBMS_MODAL" }, "*");
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
console.log("[PBMS] Cancel clicked → closing modal");
|
||||
window.parent.postMessage({ action: "CLOSE_PBMS_MODAL" }, "*");
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -61,32 +135,75 @@ export default function PreferenceBasedModelSelector() {
|
|||
<Card>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="routingEnabled" className="font-medium">Enable preference-based routing</Label>
|
||||
<Label htmlFor="routingEnabled" className="font-medium">
|
||||
Enable preference-based routing
|
||||
</Label>
|
||||
<Switch id="routingEnabled" checked={routingEnabled} onCheckedChange={setRoutingEnabled} />
|
||||
</div>
|
||||
{routingEnabled && (
|
||||
<div className="pt-4 mt-4 space-y-3 border-t border-gray-200">
|
||||
{preferences.map((pref) => (
|
||||
<div key={pref.id} className="grid grid-cols-[1fr_1fr_auto] gap-2 items-center">
|
||||
<Input placeholder="e.g., summarize articles" value={pref.naturalLanguage} onChange={(e) => updatePreference(pref.id, "naturalLanguage", e.target.value)} />
|
||||
<select value={pref.model} onChange={(e) => updatePreference(pref.id, "model", e.target.value)} className="w-full px-3 py-2 text-sm text-gray-800 bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"><option disabled value="">Select Model</option>{modelOptions.map((model) => (<option key={model} value={model}>{model}</option>))}</select>
|
||||
<Button variant="ghost" size="icon" onClick={() => removePreference(pref.id)} className="text-gray-500 hover:text-red-600 disabled:opacity-50" disabled={preferences.length <= 1}><Trash2 className="h-4 w-4" /></Button>
|
||||
<Input
|
||||
placeholder="e.g., summarize articles"
|
||||
value={pref.naturalLanguage}
|
||||
onChange={(e) => updatePreference(pref.id, "naturalLanguage", e.target.value)}
|
||||
/>
|
||||
<select
|
||||
value={pref.model}
|
||||
onChange={(e) => updatePreference(pref.id, "model", e.target.value)}
|
||||
className="w-full px-3 py-2 text-sm text-gray-800 bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option disabled value="">
|
||||
Select Model
|
||||
</option>
|
||||
{modelOptions.map((model) => (
|
||||
<option key={model} value={model}>
|
||||
{model}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removePreference(pref.id)}
|
||||
className="text-gray-500 hover:text-red-600 disabled:opacity-50"
|
||||
disabled={preferences.length <= 1}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" onClick={addPreference} className="flex gap-2 items-center text-sm mt-2"><PlusCircle className="h-4 w-4" /> Add Preference</Button>
|
||||
<Button variant="outline" onClick={addPreference} className="flex gap-2 items-center text-sm mt-2">
|
||||
<PlusCircle className="h-4 w-4" /> Add Preference
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Label htmlFor="defaultModel" className="font-medium">Default Model on Page Load</Label>
|
||||
<select id="defaultModel" value={defaultModel} onChange={(e) => setDefaultModel(e.target.value)} className="w-full mt-2 px-3 py-2 text-sm text-gray-800 bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">{modelOptions.map((model) => (<option key={model} value={model}>{model}</option>))}
|
||||
<Label htmlFor="defaultModel" className="font-medium">
|
||||
Default Model on Page Load
|
||||
</Label>
|
||||
<select
|
||||
id="defaultModel"
|
||||
value={defaultModel}
|
||||
onChange={(e) => setDefaultModel(e.target.value)}
|
||||
className="w-full mt-2 px-3 py-2 text-sm text-gray-800 bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
{modelOptions.map((model) => (
|
||||
<option key={model} value={model}>
|
||||
{model}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
|
||||
<Button variant="ghost" onClick={() => window.close()}>Cancel</Button>
|
||||
<Button variant="ghost" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave}>Save and Apply</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue