spb/zyquo-cloud-web Public MIT
Zyquo Cloud Web — every cloud model, one beautiful chat, entirely in your browser.
TypeScript 81.9%
CSS 8.9%
JavaScript 7.5%
Shell 1.1%
HTML 0.6%
1/*2 * ParamsPanel.tsx3 * Zyquo Cloud Web4 *5 * Author: Simon-Pierre Boucher6 * Mail: contact@spboucher.ai7 *8 * Per-conversation parameter editor. Only shows controls the active model's9 * ParameterSupport allows; every control is gated by an enable checkbox —10 * unchecked means "provider default" and the key is omitted from requests11 * entirely (native behavior).12 */1314import { findModel } from '../features/catalogHelpers'15import { useStore } from '../state/store'16import type { ChatParameters, Conversation } from '../types'1718export default function ParamsPanel({19 conversation,20 onClose,21}: {22 conversation: Conversation23 onClose: () => void24}) {25 const updateConversation = useStore((s) => s.updateConversation)26 const model = findModel(conversation.provider, conversation.modelID)27 const support = model?.parameterSupport28 const params = conversation.parameters2930 const patch = (p: Partial<ChatParameters>) => {31 const merged = { ...params, ...p }32 for (const key of Object.keys(merged) as (keyof ChatParameters)[]) {33 if (merged[key] === undefined) delete merged[key]34 }35 updateConversation(conversation.id, { parameters: merged })36 }3738 const slider = (39 label: string,40 key: 'temperature' | 'topP' | 'frequencyPenalty' | 'presencePenalty',41 min: number,42 max: number,43 step: number,44 fallback: number45 ) => (46 <div className="settings-row" key={key}>47 <label style={{ display: 'flex', alignItems: 'center', gap: 6, width: 150 }}>48 <input49 type="checkbox"50 checked={params[key] !== undefined}51 onChange={(e) => patch({ [key]: e.target.checked ? fallback : undefined })}52 />53 {label}54 </label>55 {params[key] !== undefined ? (56 <>57 <input58 type="range"59 min={min}60 max={max}61 step={step}62 value={params[key]}63 style={{ flex: 1, accentColor: 'var(--z-accent)' }}64 onChange={(e) => patch({ [key]: Number(e.target.value) })}65 />66 <span style={{ width: 40, textAlign: 'right', fontSize: 12 }}>67 {params[key]?.toFixed(2)}68 </span>69 </>70 ) : (71 <span className="settings-note" style={{ marginLeft: 'auto' }}>72 default73 </span>74 )}75 </div>76 )7778 return (79 <div className="overlay" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>80 <div className="panel" style={{ width: 420 }}>81 <div className="settings-content">82 <h3>Parameters — {model?.displayName ?? conversation.modelID}</h3>83 {support?.temperature && slider('Temperature', 'temperature', 0, 2, 0.05, 1)}84 {support?.topP && slider('Top P', 'topP', 0, 1, 0.01, 1)}85 {support?.frequencyPenalty &&86 slider('Frequency penalty', 'frequencyPenalty', -2, 2, 0.1, 0)}87 {support?.presencePenalty && slider('Presence penalty', 'presencePenalty', -2, 2, 0.1, 0)}88 <div className="settings-row">89 <span style={{ width: 150 }}>Max tokens</span>90 <input91 className="number-input"92 type="number"93 min={1}94 placeholder="default"95 style={{ width: 100 }}96 value={params.maxTokens ?? ''}97 onChange={(e) =>98 patch({ maxTokens: e.target.value === '' ? undefined : Number(e.target.value) })99 }100 />101 </div>102 {support?.reasoningEffort && (103 <div className="settings-row">104 <span style={{ width: 150 }}>Reasoning effort</span>105 <select106 className="select"107 value={params.reasoningEffort ?? ''}108 onChange={(e) =>109 patch({110 reasoningEffort:111 e.target.value === ''112 ? undefined113 : (e.target.value as 'low' | 'medium' | 'high'),114 })115 }116 >117 <option value="">Default</option>118 <option value="low">Low</option>119 <option value="medium">Medium</option>120 <option value="high">High</option>121 </select>122 </div>123 )}124 {support?.thinkingToggle && (125 <div className="settings-row">126 <label style={{ display: 'flex', alignItems: 'center', gap: 6 }}>127 <input128 type="checkbox"129 checked={params.thinkingEnabled ?? false}130 onChange={(e) =>131 patch({ thinkingEnabled: e.target.checked ? true : undefined })132 }133 />134 Extended thinking135 </label>136 </div>137 )}138 <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>139 <button className="button" onClick={onClose}>140 Done141 </button>142 </div>143 </div>144 </div>145 </div>146 )147}148