feat: autotag mixed-bag splitting, search relevance fixes, and multi-library download imports
Autotag: detect "junk drawer" folders with no artist/album consensus and split them into synthetic per-cluster groups instead of forcing one match on an unrelated pile of tracks; repair tagging_items rows left behind by a prior scan orphan-cleanup gap. Explore: fix an exact artist-name search being drowned out by its own catalog entries in intent-prior scoring, and prune stale in_library bookkeeping left behind when a referenced library row is deleted. Download: fix a multi-library regression where every import failed with "no library root configured" — the importer resolved the library root from a legacy single-library config field that nothing populates in the current multi-library model. It now resolves the destination library per-request from the request's own library_id. Also widen the Soulseek search window (12s -> 20s), measured against real request history to be missing available peers on live queries. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2Agd9af5hE7qzti2ackiS
This commit is contained in:
@@ -11,8 +11,10 @@ import { designTokens } from '../../styles/tokens.css';
|
||||
import type {
|
||||
DownloadDescriptor,
|
||||
DownloadProvider,
|
||||
ProviderField,
|
||||
} from '@store/download-store';
|
||||
import { downloadStore } from '@store/download-store';
|
||||
import { DirectoryPicker } from '@go/frontendutil/FrontendUtil';
|
||||
import './config-section';
|
||||
|
||||
/**
|
||||
@@ -162,6 +164,20 @@ export class DownloadClients extends LitElement {
|
||||
.add-row {
|
||||
margin-top: 0.8em;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.field-row wa-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.field-row .browse-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -257,7 +273,7 @@ export class DownloadClients extends LitElement {
|
||||
<wa-select
|
||||
label="Client type"
|
||||
.value=${this.newKind}
|
||||
@wa-change=${this.onKindChange}
|
||||
@change=${this.onKindChange}
|
||||
>
|
||||
${this.descriptors.map(
|
||||
(d) => html`<wa-option value=${d.kind}>${d.name}</wa-option>`,
|
||||
@@ -277,7 +293,7 @@ export class DownloadClients extends LitElement {
|
||||
<wa-input
|
||||
label="Name"
|
||||
.value=${this.draftName}
|
||||
@wa-input=${(e: Event) => {
|
||||
@input=${(e: Event) => {
|
||||
this.draftName = (e.target as HTMLInputElement).value;
|
||||
}}
|
||||
></wa-input>
|
||||
@@ -311,25 +327,25 @@ export class DownloadClients extends LitElement {
|
||||
<wa-input
|
||||
label="Name"
|
||||
.value=${this.draftName}
|
||||
@wa-input=${(e: Event) => {
|
||||
@input=${(e: Event) => {
|
||||
this.draftName = (e.target as HTMLInputElement).value;
|
||||
}}
|
||||
></wa-input>
|
||||
|
||||
${descriptor ? this.renderFields(descriptor) : nothing}
|
||||
${descriptor ? this.renderFields(descriptor, provider) : nothing}
|
||||
|
||||
<wa-input
|
||||
label="Priority"
|
||||
type="number"
|
||||
.value=${String(provider.priority)}
|
||||
@wa-input=${(e: Event) => {
|
||||
@input=${(e: Event) => {
|
||||
this.draft['__priority'] = (e.target as HTMLInputElement).value;
|
||||
}}
|
||||
></wa-input>
|
||||
|
||||
<wa-switch
|
||||
?checked=${provider.enabled}
|
||||
@wa-change=${(e: Event) => {
|
||||
@change=${(e: Event) => {
|
||||
this.draft['__enabled'] = (e.target as HTMLInputElement)
|
||||
.checked
|
||||
? '1'
|
||||
@@ -355,28 +371,61 @@ export class DownloadClients extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
/** Renders one input per descriptor field. */
|
||||
private renderFields(descriptor: DownloadDescriptor) {
|
||||
return (descriptor.fields ?? []).map(
|
||||
(field) => html`
|
||||
<wa-input
|
||||
label=${field.label}
|
||||
placeholder=${field.placeholder ?? ''}
|
||||
type=${field.secret ? 'password' : 'text'}
|
||||
.value=${this.draft[field.key] ?? ''}
|
||||
@wa-input=${(e: Event) => {
|
||||
this.draft = {
|
||||
...this.draft,
|
||||
[field.key]: (e.target as HTMLInputElement).value,
|
||||
};
|
||||
}}
|
||||
>
|
||||
${field.help ? html`<span slot="hint">${field.help}</span>` : nothing}
|
||||
</wa-input>
|
||||
`,
|
||||
);
|
||||
/** Renders one input per descriptor field, plus a folder browse
|
||||
* button for path fields and an "already set" placeholder for
|
||||
* secrets the provider already has a stored value for. */
|
||||
private renderFields(descriptor: DownloadDescriptor, provider?: DownloadProvider) {
|
||||
return (descriptor.fields ?? []).map((field) => {
|
||||
const isSet = field.secret && provider?.setSecrets?.[field.key];
|
||||
const placeholder = isSet
|
||||
? '•••••••• (unchanged — enter a new value to replace it)'
|
||||
: (field.placeholder ?? '');
|
||||
|
||||
return html`
|
||||
<div class="field-row">
|
||||
<wa-input
|
||||
label=${field.label}
|
||||
placeholder=${placeholder}
|
||||
type=${field.secret ? 'password' : 'text'}
|
||||
.value=${this.draft[field.key] ?? ''}
|
||||
@input=${(e: Event) => {
|
||||
this.draft = {
|
||||
...this.draft,
|
||||
[field.key]: (e.target as HTMLInputElement).value,
|
||||
};
|
||||
}}
|
||||
>
|
||||
${field.help ? html`<span slot="hint">${field.help}</span>` : nothing}
|
||||
</wa-input>
|
||||
${field.path
|
||||
? html`
|
||||
<wa-button
|
||||
size="small"
|
||||
appearance="outlined"
|
||||
class="browse-button"
|
||||
@click=${() => this.browseForFolder(field)}
|
||||
>
|
||||
Browse
|
||||
</wa-button>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
private browseForFolder = async (field: ProviderField) => {
|
||||
try {
|
||||
const dir = await DirectoryPicker();
|
||||
|
||||
if (dir) {
|
||||
this.draft = { ...this.draft, [field.key]: dir };
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to open directory picker:', err);
|
||||
}
|
||||
};
|
||||
|
||||
private descriptorFor(kind: string): DownloadDescriptor | undefined {
|
||||
return this.descriptors.find((d) => d.kind === kind);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user