SPB Git

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%
5.0 KB · 148 lines tsx
Raw Blame History
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