decompose application.py into enrichment, scholar processing, run completion

This commit is contained in:
Justin Visser 2026-02-27 14:19:39 +01:00
parent 9a7fa5004e
commit 6379c97e90
28 changed files with 2982 additions and 2576 deletions

View file

@ -34,6 +34,13 @@ export interface UserSettingsUpdate {
crossref_api_mailto: string | null;
}
export interface AdminScholarHttpSettings {
user_agent: string;
rotate_user_agent: boolean;
accept_language: string;
cookie: string;
}
export interface ChangePasswordPayload {
current_password: string;
new_password: string;
@ -60,3 +67,20 @@ export async function changePassword(payload: ChangePasswordPayload): Promise<{
});
return response.data;
}
export async function fetchAdminScholarHttpSettings(): Promise<AdminScholarHttpSettings> {
const response = await apiRequest<AdminScholarHttpSettings>("/admin/settings/scholar-http", {
method: "GET",
});
return response.data;
}
export async function updateAdminScholarHttpSettings(
payload: AdminScholarHttpSettings,
): Promise<AdminScholarHttpSettings> {
const response = await apiRequest<AdminScholarHttpSettings>("/admin/settings/scholar-http", {
method: "PUT",
body: payload,
});
return response.data;
}

View file

@ -14,9 +14,12 @@ import AppTabs, { type AppTabItem } from "@/components/ui/AppTabs.vue";
import SettingsAdminPanel from "@/features/settings/SettingsAdminPanel.vue";
import {
changePassword,
fetchAdminScholarHttpSettings,
fetchSettings,
type AdminScholarHttpSettings,
type UserSettings,
type UserSettingsUpdate,
updateAdminScholarHttpSettings,
updateSettings,
} from "@/features/settings";
import { ApiRequestError } from "@/lib/api/errors";
@ -40,6 +43,7 @@ const router = useRouter();
const loading = ref(true);
const saving = ref(false);
const savingScholarHttp = ref(false);
const updatingPassword = ref(false);
const autoRunEnabled = ref(false);
@ -49,6 +53,10 @@ const navVisiblePages = ref<string[]>([]);
const openalexApiKey = ref("");
const crossrefApiToken = ref("");
const crossrefApiMailto = ref("");
const scholarHttpUserAgent = ref("");
const scholarHttpRotateUserAgent = ref(false);
const scholarHttpAcceptLanguage = ref("en-US,en;q=0.9");
const scholarHttpCookie = ref("");
const currentPassword = ref("");
const newPassword = ref("");
@ -142,6 +150,13 @@ function hydrateSettings(settings: UserSettings): void {
runStatus.setSafetyState(settings.safety_state);
}
function hydrateScholarHttpSettings(settings: AdminScholarHttpSettings): void {
scholarHttpUserAgent.value = settings.user_agent;
scholarHttpRotateUserAgent.value = Boolean(settings.rotate_user_agent);
scholarHttpAcceptLanguage.value = settings.accept_language;
scholarHttpCookie.value = settings.cookie;
}
function parseBoundedInteger(value: string, label: string, minimum: number): number {
const parsed = Number(value);
if (!Number.isInteger(parsed)) {
@ -160,6 +175,9 @@ async function loadSettings(): Promise<void> {
try {
const settings = await fetchSettings();
hydrateSettings(settings);
if (auth.isAdmin) {
hydrateScholarHttpSettings(await fetchAdminScholarHttpSettings());
}
} catch (error) {
if (error instanceof ApiRequestError) {
errorMessage.value = error.message;
@ -172,6 +190,32 @@ async function loadSettings(): Promise<void> {
}
}
async function onSaveScholarHttpSettings(): Promise<void> {
savingScholarHttp.value = true;
errorMessage.value = null;
errorRequestId.value = null;
successMessage.value = null;
try {
const updated = await updateAdminScholarHttpSettings({
user_agent: scholarHttpUserAgent.value.trim(),
rotate_user_agent: scholarHttpRotateUserAgent.value,
accept_language: scholarHttpAcceptLanguage.value.trim(),
cookie: scholarHttpCookie.value.trim(),
});
hydrateScholarHttpSettings(updated);
successMessage.value = "Scholar HTTP profile updated.";
} catch (error) {
if (error instanceof ApiRequestError) {
errorMessage.value = error.message;
errorRequestId.value = error.requestId;
} else {
errorMessage.value = "Unable to save Scholar HTTP profile.";
}
} finally {
savingScholarHttp.value = false;
}
}
async function onSaveSettings(): Promise<void> {
saving.value = true;
errorMessage.value = null;
@ -404,6 +448,49 @@ onMounted(async () => {
<AppInput v-model="crossrefApiMailto" placeholder="e.g. admin@yourdomain.com" type="email" autocomplete="off" />
</label>
</div>
<div class="grid gap-2 rounded-lg border border-stroke-default bg-surface-card-muted p-3">
<h3 class="text-sm font-semibold text-ink-secondary">Scholar Request Profile</h3>
<p class="text-xs text-secondary mb-2">
Tune Scholar HTTP fingerprint behavior used by live scraper requests. Changes apply to new runs.
</p>
<label class="grid gap-1 text-sm font-medium text-ink-secondary">
<span>User-Agent override</span>
<AppInput
v-model="scholarHttpUserAgent"
placeholder="Leave empty to use built-in browser-like user agent"
autocomplete="off"
/>
</label>
<label class="grid gap-1 text-sm font-medium text-ink-secondary mt-2">
<span>Accept-Language</span>
<AppInput
v-model="scholarHttpAcceptLanguage"
placeholder="e.g. en-US,en;q=0.9"
autocomplete="off"
/>
</label>
<label class="grid gap-1 text-sm font-medium text-ink-secondary mt-2">
<span>Cookie header</span>
<AppInput
v-model="scholarHttpCookie"
placeholder="Optional. Leave empty to disable cookie passthrough"
autocomplete="off"
/>
</label>
<div class="mt-2">
<AppCheckbox
id="scholar-http-rotate-user-agent"
v-model="scholarHttpRotateUserAgent"
label="Rotate user-agent per request"
/>
</div>
<div>
<AppButton :disabled="savingScholarHttp" @click="onSaveScholarHttpSettings">
{{ savingScholarHttp ? "Saving..." : "Save Scholar request profile" }}
</AppButton>
</div>
</div>
</div>
<div>