|
|
|
|
@@ -1,5 +1,6 @@
|
|
|
|
|
/**
|
|
|
|
|
* Dedicated settings screen for providers, task model bindings, and catalog controls.
|
|
|
|
|
* Uses concise helper hints for advanced runtime and provider settings.
|
|
|
|
|
*/
|
|
|
|
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
|
|
import type { JSX } from 'react';
|
|
|
|
|
@@ -67,7 +68,7 @@ function clampProcessingLogUnboundEntries(value: number): number {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Renders compact human-oriented settings controls.
|
|
|
|
|
* Renders compact human-oriented settings controls with plain-language hints.
|
|
|
|
|
*/
|
|
|
|
|
export default function SettingsScreen({
|
|
|
|
|
settings,
|
|
|
|
|
@@ -367,6 +368,7 @@ export default function SettingsScreen({
|
|
|
|
|
onChange={(nextPath) => setUploadDefaults({ ...uploadDefaults, logical_path: nextPath })}
|
|
|
|
|
suggestions={knownPaths}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">Used when you upload without choosing a path.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field settings-field-wide">
|
|
|
|
|
Default Tags
|
|
|
|
|
@@ -375,6 +377,7 @@ export default function SettingsScreen({
|
|
|
|
|
onChange={(nextTags) => setUploadDefaults({ ...uploadDefaults, tags: nextTags })}
|
|
|
|
|
suggestions={knownTags}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">Added automatically when no tags are selected.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Cards Per Page
|
|
|
|
|
@@ -385,7 +388,14 @@ export default function SettingsScreen({
|
|
|
|
|
value={cardsPerPageInput}
|
|
|
|
|
onChange={(event) => setCardsPerPageInput(event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">Controls how many documents you see at once.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<div className="settings-subsection-divider">
|
|
|
|
|
<h4>Processing Log Controls</h4>
|
|
|
|
|
<p className="small">
|
|
|
|
|
These settings affect processing logs only. They do not change default path, tags, or document cards.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Keep document sessions
|
|
|
|
|
<input
|
|
|
|
|
@@ -403,6 +413,7 @@ export default function SettingsScreen({
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">How many recent log sessions to keep for each document.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Keep unbound entries
|
|
|
|
|
@@ -421,8 +432,9 @@ export default function SettingsScreen({
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">How many standalone log entries to keep.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="inline-checkbox settings-checkbox-field">
|
|
|
|
|
<label className="inline-checkbox settings-checkbox-field settings-checkbox-with-hint">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={displaySettings.log_typing_animation_enabled}
|
|
|
|
|
@@ -430,7 +442,10 @@ export default function SettingsScreen({
|
|
|
|
|
setDisplaySettings({ ...displaySettings, log_typing_animation_enabled: event.target.checked })
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-checkbox-copy">
|
|
|
|
|
Processing log typing animation enabled
|
|
|
|
|
<span className="settings-field-hint">Shows new log text as a type-in animation.</span>
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
<p className="small settings-helper-text">
|
|
|
|
|
Processing-log retention values are used by backend trim routines when pruning historical entries.
|
|
|
|
|
@@ -565,6 +580,7 @@ export default function SettingsScreen({
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">Task settings use this ID to select the provider.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Label
|
|
|
|
|
@@ -596,6 +612,7 @@ export default function SettingsScreen({
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">Stop waiting after this many seconds if a call hangs.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field settings-field-wide">
|
|
|
|
|
Base URL
|
|
|
|
|
@@ -609,6 +626,7 @@ export default function SettingsScreen({
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">API endpoint root for this provider.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field settings-field-wide">
|
|
|
|
|
API Key
|
|
|
|
|
@@ -624,6 +642,7 @@ export default function SettingsScreen({
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">Leave blank to keep the current stored key.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="inline-checkbox settings-checkbox-field">
|
|
|
|
|
<input
|
|
|
|
|
@@ -678,10 +697,12 @@ export default function SettingsScreen({
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Model
|
|
|
|
|
<input value={ocrTask.model} onChange={(event) => setOcrTask({ ...ocrTask, model: event.target.value })} />
|
|
|
|
|
<span className="settings-field-hint">Model name sent to the selected provider.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field settings-field-wide">
|
|
|
|
|
OCR Prompt
|
|
|
|
|
<textarea value={ocrTask.prompt} onChange={(event) => setOcrTask({ ...ocrTask, prompt: event.target.value })} />
|
|
|
|
|
<span className="settings-field-hint">Instructions used when reading handwriting text.</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -708,6 +729,7 @@ export default function SettingsScreen({
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Model
|
|
|
|
|
<input value={summaryTask.model} onChange={(event) => setSummaryTask({ ...summaryTask, model: event.target.value })} />
|
|
|
|
|
<span className="settings-field-hint">Model name sent to the selected provider.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Max Input Tokens
|
|
|
|
|
@@ -723,10 +745,12 @@ export default function SettingsScreen({
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<span className="settings-field-hint">Long inputs are trimmed to this size before summarizing.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field settings-field-wide">
|
|
|
|
|
Summary Prompt
|
|
|
|
|
<textarea value={summaryTask.prompt} onChange={(event) => setSummaryTask({ ...summaryTask, prompt: event.target.value })} />
|
|
|
|
|
<span className="settings-field-hint">Instructions that shape the generated summary.</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -753,42 +777,56 @@ export default function SettingsScreen({
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Model
|
|
|
|
|
<input value={routingTask.model} onChange={(event) => setRoutingTask({ ...routingTask, model: event.target.value })} />
|
|
|
|
|
<span className="settings-field-hint">Model name sent to the selected provider.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Neighbor Count
|
|
|
|
|
<input type="number" value={routingTask.neighbor_count} onChange={(event) => setRoutingTask({ ...routingTask, neighbor_count: Number.parseInt(event.target.value, 10) || routingTask.neighbor_count })} />
|
|
|
|
|
<span className="settings-field-hint">How many close matches to compare before routing.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Min Neighbor Similarity
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={routingTask.neighbor_min_similarity} onChange={(event) => setRoutingTask({ ...routingTask, neighbor_min_similarity: Number.parseFloat(event.target.value) || routingTask.neighbor_min_similarity })} />
|
|
|
|
|
<span className="settings-field-hint">Ignore neighbors below this match score.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Auto Apply Confidence
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={routingTask.auto_apply_confidence_threshold} onChange={(event) => setRoutingTask({ ...routingTask, auto_apply_confidence_threshold: Number.parseFloat(event.target.value) || routingTask.auto_apply_confidence_threshold })} />
|
|
|
|
|
<span className="settings-field-hint">Minimum model confidence for automatic changes.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Auto Apply Neighbor Similarity
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={routingTask.auto_apply_neighbor_similarity_threshold} onChange={(event) => setRoutingTask({ ...routingTask, auto_apply_neighbor_similarity_threshold: Number.parseFloat(event.target.value) || routingTask.auto_apply_neighbor_similarity_threshold })} />
|
|
|
|
|
<span className="settings-field-hint">Minimum neighbor score for automatic changes.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="inline-checkbox settings-checkbox-field">
|
|
|
|
|
<label className="inline-checkbox settings-checkbox-field settings-checkbox-with-hint">
|
|
|
|
|
<input type="checkbox" checked={routingTask.neighbor_path_override_enabled} onChange={(event) => setRoutingTask({ ...routingTask, neighbor_path_override_enabled: event.target.checked })} />
|
|
|
|
|
<span className="settings-checkbox-copy">
|
|
|
|
|
Dominant neighbor path override enabled
|
|
|
|
|
<span className="settings-field-hint">
|
|
|
|
|
If a strong top match disagrees with the model, use the top match path instead.
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Override Min Similarity
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={routingTask.neighbor_path_override_min_similarity} onChange={(event) => setRoutingTask({ ...routingTask, neighbor_path_override_min_similarity: Number.parseFloat(event.target.value) || routingTask.neighbor_path_override_min_similarity })} />
|
|
|
|
|
<span className="settings-field-hint">Top neighbor must reach this score to override.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Override Min Gap
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={routingTask.neighbor_path_override_min_gap} onChange={(event) => setRoutingTask({ ...routingTask, neighbor_path_override_min_gap: Number.parseFloat(event.target.value) || routingTask.neighbor_path_override_min_gap })} />
|
|
|
|
|
<span className="settings-field-hint">Top match must beat the second match by at least this gap.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Override Max LLM Confidence
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={routingTask.neighbor_path_override_max_confidence} onChange={(event) => setRoutingTask({ ...routingTask, neighbor_path_override_max_confidence: Number.parseFloat(event.target.value) || routingTask.neighbor_path_override_max_confidence })} />
|
|
|
|
|
<span className="settings-field-hint">Override only runs when model confidence is at or below this level.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field settings-field-wide">
|
|
|
|
|
Routing Prompt
|
|
|
|
|
<textarea value={routingTask.prompt} onChange={(event) => setRoutingTask({ ...routingTask, prompt: event.target.value })} />
|
|
|
|
|
<span className="settings-field-hint">Instructions used when deciding document path and tags.</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -805,26 +843,32 @@ export default function SettingsScreen({
|
|
|
|
|
<label className="settings-field settings-field-wide">
|
|
|
|
|
Typesense Embedding Model Slug
|
|
|
|
|
<input value={handwritingStyle.embed_model} onChange={(event) => setHandwritingStyle({ ...handwritingStyle, embed_model: event.target.value })} />
|
|
|
|
|
<span className="settings-field-hint">Embedding model used to compare handwriting style similarity.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Neighbor Limit
|
|
|
|
|
<input type="number" min={1} max={32} value={handwritingStyle.neighbor_limit} onChange={(event) => setHandwritingStyle({ ...handwritingStyle, neighbor_limit: Number.parseInt(event.target.value, 10) || handwritingStyle.neighbor_limit })} />
|
|
|
|
|
<span className="settings-field-hint">How many nearby samples to check during matching.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Match Min Similarity
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={handwritingStyle.match_min_similarity} onChange={(event) => setHandwritingStyle({ ...handwritingStyle, match_min_similarity: Number.parseFloat(event.target.value) || handwritingStyle.match_min_similarity })} />
|
|
|
|
|
<span className="settings-field-hint">Minimum similarity needed to treat two styles as a match.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Bootstrap Match Min Similarity
|
|
|
|
|
<input type="number" step="0.01" min="0" max="1" value={handwritingStyle.bootstrap_match_min_similarity} onChange={(event) => setHandwritingStyle({ ...handwritingStyle, bootstrap_match_min_similarity: Number.parseFloat(event.target.value) || handwritingStyle.bootstrap_match_min_similarity })} />
|
|
|
|
|
<span className="settings-field-hint">Stricter match score used only while bootstrapping new clusters.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Bootstrap Sample Size
|
|
|
|
|
<input type="number" min={1} max={30} value={handwritingStyle.bootstrap_sample_size} onChange={(event) => setHandwritingStyle({ ...handwritingStyle, bootstrap_sample_size: Number.parseInt(event.target.value, 10) || handwritingStyle.bootstrap_sample_size })} />
|
|
|
|
|
<span className="settings-field-hint">Number of samples used to start each new style cluster.</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
Max Image Side (px)
|
|
|
|
|
<input type="number" min={256} max={4096} value={handwritingStyle.image_max_side} onChange={(event) => setHandwritingStyle({ ...handwritingStyle, image_max_side: Number.parseInt(event.target.value, 10) || handwritingStyle.image_max_side })} />
|
|
|
|
|
<span className="settings-field-hint">Resizes large images to this limit before analysis.</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|