add configurable max_tokens per AI activity
Allows users to configure the max_tokens sent to the AI endpoint for each activity (tagging, description, extraction, translation) individually, with per-library overrides following the same pattern as model overrides. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,10 @@ export async function PUT(
|
||||
promptTagger: typeof body.promptTagger === 'string' ? body.promptTagger : undefined,
|
||||
promptExtract: typeof body.promptExtract === 'string' ? body.promptExtract : undefined,
|
||||
promptTranslate: typeof body.promptTranslate === 'string' ? body.promptTranslate : undefined,
|
||||
maxTokensTag: typeof body.maxTokensTag === 'number' ? body.maxTokensTag : (body.maxTokensTag === null ? null : undefined),
|
||||
maxTokensDescribe: typeof body.maxTokensDescribe === 'number' ? body.maxTokensDescribe : (body.maxTokensDescribe === null ? null : undefined),
|
||||
maxTokensExtract: typeof body.maxTokensExtract === 'number' ? body.maxTokensExtract : (body.maxTokensExtract === null ? null : undefined),
|
||||
maxTokensTranslate: typeof body.maxTokensTranslate === 'number' ? body.maxTokensTranslate : (body.maxTokensTranslate === null ? null : undefined),
|
||||
})
|
||||
|
||||
return NextResponse.json(getLibraryAiOverrides(id))
|
||||
|
||||
@@ -30,6 +30,10 @@ export async function PUT(request: NextRequest) {
|
||||
promptExtract?: string
|
||||
promptTranslate?: string
|
||||
maxRetries?: number
|
||||
maxTokensTag?: number
|
||||
maxTokensDescribe?: number
|
||||
maxTokensExtract?: number
|
||||
maxTokensTranslate?: number
|
||||
}
|
||||
try {
|
||||
body = await request.json()
|
||||
@@ -42,6 +46,7 @@ export async function PUT(request: NextRequest) {
|
||||
modelTagging, modelDescribe, modelExtract, modelTranslate,
|
||||
promptDescribe, promptTagger, promptExtract, promptTranslate,
|
||||
maxRetries,
|
||||
maxTokensTag, maxTokensDescribe, maxTokensExtract, maxTokensTranslate,
|
||||
} = body
|
||||
|
||||
if (typeof endpoint !== 'string') {
|
||||
@@ -66,6 +71,10 @@ export async function PUT(request: NextRequest) {
|
||||
typeof promptTagger === 'string' ? promptTagger : undefined,
|
||||
typeof promptExtract === 'string' ? promptExtract : undefined,
|
||||
typeof promptTranslate === 'string' ? promptTranslate : undefined,
|
||||
typeof maxTokensTag === 'number' ? maxTokensTag : undefined,
|
||||
typeof maxTokensDescribe === 'number' ? maxTokensDescribe : undefined,
|
||||
typeof maxTokensExtract === 'number' ? maxTokensExtract : undefined,
|
||||
typeof maxTokensTranslate === 'number' ? maxTokensTranslate : undefined,
|
||||
)
|
||||
|
||||
if (typeof preferredLanguage === 'string' && preferredLanguage.trim()) {
|
||||
|
||||
@@ -16,6 +16,10 @@ interface AiSettings {
|
||||
promptExtract: string
|
||||
promptTranslate: string
|
||||
maxRetries: number
|
||||
maxTokensTag: number
|
||||
maxTokensDescribe: number
|
||||
maxTokensExtract: number
|
||||
maxTokensTranslate: number
|
||||
}
|
||||
|
||||
interface AiJob {
|
||||
@@ -47,6 +51,10 @@ interface LibraryOverride {
|
||||
promptTagger: string
|
||||
promptExtract: string
|
||||
promptTranslate: string
|
||||
maxTokensTag: number | null
|
||||
maxTokensDescribe: number | null
|
||||
maxTokensExtract: number | null
|
||||
maxTokensTranslate: number | null
|
||||
}
|
||||
|
||||
function formatElapsed(startedAt: number): string {
|
||||
@@ -67,6 +75,7 @@ export default function AiTaggingPage() {
|
||||
enabled: false, preferredLanguage: 'English',
|
||||
promptDescribe: '', promptTagger: '', promptExtract: '', promptTranslate: '',
|
||||
maxRetries: 3,
|
||||
maxTokensTag: 8192, maxTokensDescribe: 8192, maxTokensExtract: 8192, maxTokensTranslate: 8192,
|
||||
})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
@@ -296,7 +305,7 @@ export default function AiTaggingPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const updateLibraryOverride = (libraryId: string, field: keyof LibraryOverride, value: string) => {
|
||||
const updateLibraryOverride = (libraryId: string, field: keyof LibraryOverride, value: string | number | null) => {
|
||||
setLibraryOverrides((prev) => ({
|
||||
...prev,
|
||||
[libraryId]: { ...(prev[libraryId] ?? emptyOverride()), [field]: value },
|
||||
@@ -544,6 +553,25 @@ export default function AiTaggingPage() {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Tagging Max Tokens">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={settings.maxTokensTag}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({ ...s, maxTokensTag: Math.max(1, parseInt(e.target.value) || 8192) }))
|
||||
}
|
||||
className="w-32 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2"
|
||||
style={{
|
||||
backgroundColor: 'var(--background)',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
onFocus={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--accent)')}
|
||||
onBlur={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--border)')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Description Model">
|
||||
<input
|
||||
type="text"
|
||||
@@ -561,6 +589,25 @@ export default function AiTaggingPage() {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Description Max Tokens">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={settings.maxTokensDescribe}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({ ...s, maxTokensDescribe: Math.max(1, parseInt(e.target.value) || 8192) }))
|
||||
}
|
||||
className="w-32 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2"
|
||||
style={{
|
||||
backgroundColor: 'var(--background)',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
onFocus={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--accent)')}
|
||||
onBlur={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--border)')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Text Extraction Model">
|
||||
<input
|
||||
type="text"
|
||||
@@ -578,6 +625,25 @@ export default function AiTaggingPage() {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Text Extraction Max Tokens">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={settings.maxTokensExtract}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({ ...s, maxTokensExtract: Math.max(1, parseInt(e.target.value) || 8192) }))
|
||||
}
|
||||
className="w-32 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2"
|
||||
style={{
|
||||
backgroundColor: 'var(--background)',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
onFocus={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--accent)')}
|
||||
onBlur={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--border)')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Translation Model">
|
||||
<input
|
||||
type="text"
|
||||
@@ -595,6 +661,25 @@ export default function AiTaggingPage() {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Translation Max Tokens">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={settings.maxTokensTranslate}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({ ...s, maxTokensTranslate: Math.max(1, parseInt(e.target.value) || 8192) }))
|
||||
}
|
||||
className="w-32 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2"
|
||||
style={{
|
||||
backgroundColor: 'var(--background)',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
onFocus={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--accent)')}
|
||||
onBlur={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--border)')}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Automatic Tagging">
|
||||
<label className="flex items-center gap-3 cursor-pointer select-none">
|
||||
<div
|
||||
@@ -890,7 +975,7 @@ export default function AiTaggingPage() {
|
||||
<Field key={field} label={label}>
|
||||
<input
|
||||
type="text"
|
||||
value={overrides[field]}
|
||||
value={overrides[field] as string}
|
||||
onChange={(e) => updateLibraryOverride(lib.id, field, e.target.value)}
|
||||
placeholder={`Leave blank to use global default${settings[field as keyof AiSettings] ? ` (${settings[field as keyof AiSettings]})` : ''}`}
|
||||
className="w-full rounded-lg px-3 py-2 text-sm font-mono outline-none focus:ring-2"
|
||||
@@ -906,6 +991,39 @@ export default function AiTaggingPage() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="text-xs font-medium uppercase tracking-wide" style={{ color: 'var(--text-secondary)' }}>Max Tokens</p>
|
||||
{(
|
||||
[
|
||||
['maxTokensTag', 'Tagging', 'maxTokensTag'] as const,
|
||||
['maxTokensDescribe', 'Description', 'maxTokensDescribe'] as const,
|
||||
['maxTokensExtract', 'Text Extraction', 'maxTokensExtract'] as const,
|
||||
['maxTokensTranslate', 'Translation', 'maxTokensTranslate'] as const,
|
||||
]
|
||||
).map(([field, label, globalField]) => (
|
||||
<Field key={field} label={label}>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={overrides[field] ?? ''}
|
||||
placeholder={`Leave blank to use global default (${settings[globalField]})`}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value
|
||||
updateLibraryOverride(lib.id, field, raw === '' ? null : Math.max(1, parseInt(raw) || 1))
|
||||
}}
|
||||
className="w-40 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2"
|
||||
style={{
|
||||
backgroundColor: 'var(--background)',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
onFocus={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--accent)')}
|
||||
onBlur={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--border)')}
|
||||
/>
|
||||
</Field>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="text-xs font-medium uppercase tracking-wide" style={{ color: 'var(--text-secondary)' }}>Prompts</p>
|
||||
{(
|
||||
@@ -919,7 +1037,7 @@ export default function AiTaggingPage() {
|
||||
<Field key={field} label={label}>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={overrides[field]}
|
||||
value={overrides[field] as string}
|
||||
onChange={(e) => updateLibraryOverride(lib.id, field, e.target.value)}
|
||||
placeholder={globalValue ? `Leave blank to use global default:\n${globalValue}` : 'Leave blank to use global default'}
|
||||
className="w-full rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 resize-y"
|
||||
@@ -1010,6 +1128,7 @@ function emptyOverride(): LibraryOverride {
|
||||
return {
|
||||
modelTagging: '', modelDescribe: '', modelExtract: '', modelTranslate: '',
|
||||
promptDescribe: '', promptTagger: '', promptExtract: '', promptTranslate: '',
|
||||
maxTokensTag: null, maxTokensDescribe: null, maxTokensExtract: null, maxTokensTranslate: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user