Fix trailing slashes in path for settings
This commit is contained in:
parent
a1ddeeef13
commit
18645d93b7
|
|
@ -61,8 +61,8 @@ const numWorkers = (() => {
|
|||
type Roots = { datasets: string; training: string; data: string };
|
||||
let rootsCache: { roots: Roots; ts: number } | null = null;
|
||||
|
||||
async function getRoots(): Promise<Roots> {
|
||||
if (rootsCache && Date.now() - rootsCache.ts < 10_000) {
|
||||
async function getRoots(forceFresh = false): Promise<Roots> {
|
||||
if (!forceFresh && rootsCache && Date.now() - rootsCache.ts < 10_000) {
|
||||
return rootsCache.roots;
|
||||
}
|
||||
const rows = await prisma.settings.findMany({
|
||||
|
|
@ -70,7 +70,9 @@ async function getRoots(): Promise<Roots> {
|
|||
});
|
||||
const fromRow = (key: string, fallback: string) => {
|
||||
const row = rows.find(r => r.key === key);
|
||||
return row?.value && row.value !== '' ? row.value : fallback;
|
||||
// path.resolve strips trailing slashes; a root stored as "/mnt/foo/" would
|
||||
// otherwise make the `root + path.sep` prefix check fail on every file.
|
||||
return path.resolve(row?.value && row.value !== '' ? row.value : fallback);
|
||||
};
|
||||
const roots: Roots = {
|
||||
datasets: fromRow('DATASETS_FOLDER', defaultDatasetsFolder),
|
||||
|
|
@ -119,11 +121,21 @@ async function serveFile(req: http.IncomingMessage, res: http.ServerResponse, pr
|
|||
.join('/');
|
||||
|
||||
let resolvedFilePath = path.resolve(decodedFilePath);
|
||||
const roots = await getRoots();
|
||||
const allowedDirs = isImg ? [roots.datasets, roots.training, roots.data] : [roots.datasets, roots.training];
|
||||
const isAllowed = allowedDirs.some(
|
||||
allowedDir => resolvedFilePath === allowedDir || resolvedFilePath.startsWith(allowedDir + path.sep),
|
||||
);
|
||||
const checkAllowed = (roots: Roots) => {
|
||||
const dirs = isImg ? [roots.datasets, roots.training, roots.data] : [roots.datasets, roots.training];
|
||||
return {
|
||||
dirs,
|
||||
allowed: dirs.some(
|
||||
allowedDir => resolvedFilePath === allowedDir || resolvedFilePath.startsWith(allowedDir + path.sep),
|
||||
),
|
||||
};
|
||||
};
|
||||
let { dirs: allowedDirs, allowed: isAllowed } = checkAllowed(await getRoots());
|
||||
if (!isAllowed) {
|
||||
// The cached roots may be stale — settings can be changed from the UI at
|
||||
// any moment. Re-fetch before denying so a just-updated path never 403s.
|
||||
({ dirs: allowedDirs, allowed: isAllowed } = checkAllowed(await getRoots(true)));
|
||||
}
|
||||
if (!isAllowed) {
|
||||
console.warn(`Access denied: ${resolvedFilePath} not in ${allowedDirs.join(', ')}`);
|
||||
res.writeHead(403);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { GroupedSelectOption, SelectOption } from "@/types";
|
||||
|
||||
type CaptionGroup = 'image' | 'music';
|
||||
type CaptionGroup = 'image' | 'music' | 'video';
|
||||
type AdditionalSections = 'caption.model_name_or_path2' | 'caption.caption_prompt' | 'caption.max_res' | 'caption.max_new_tokens' | 'caption.fixed_caption' | 'caption.thinking';
|
||||
|
||||
export interface CaptionOption {
|
||||
|
|
@ -19,11 +19,14 @@ const defaultNameOrPath = '';
|
|||
|
||||
const extensionsAudio = ['mp3', 'wav', 'flac', 'ogg'];
|
||||
const extensionsImage = ['jpg', 'jpeg', 'png', 'bmp', 'webp'];
|
||||
const extensionsVideo = ['mp4', 'mov', 'webm', 'mkv', 'avi'];
|
||||
|
||||
const defaultExtensions = [...extensionsImage];
|
||||
|
||||
const defaultImageCaptionPrompt = "Caption this image as if you were going to try to generate it with an image generator. Be thurough and describe everything in the image. Be decisive by stating things as they are. Do not say things like \"It appears that\" Or \"possibly\". Start out with things like \"A person on the beach\" or \"A black dragon\". No preamble. Just get to the point.";
|
||||
|
||||
const defaultVideoCaptionPrompt = "Caption this video as if you were going to try to generate it with a video generator. Describe the visual content, how it moves and changes over time, and the camera work. Also describe the audio, including any speech, music, or sound effects, and transcribe spoken dialogue verbatim in quotes. Be decisive by stating things as they are. Do not say things like \"It appears that\" Or \"possibly\". No preamble. Just get to the point.";
|
||||
|
||||
// Editable ADDITIONAL INSTRUCTIONS block injected into the Ideogram system prompt.
|
||||
// Users can tweak this for dataset-specific guidance without altering the fixed
|
||||
// output contract, element/background rules, or bbox format.
|
||||
|
|
@ -78,6 +81,26 @@ export const captionerTypes: CaptionOption[] = [
|
|||
'caption.thinking',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Qwen3OmniCaptioner',
|
||||
label: 'Qwen3-Omni',
|
||||
group: 'video',
|
||||
defaults: {
|
||||
'config.process[0].caption.model_name_or_path': ['ostris/Qwen3-Omni-30B-A3B-Instruct', defaultNameOrPath],
|
||||
'config.process[0].caption.extensions': [extensionsVideo, defaultExtensions],
|
||||
'config.process[0].caption.caption_prompt': [defaultVideoCaptionPrompt, undefined],
|
||||
'config.process[0].caption.max_res': [512, undefined],
|
||||
'config.process[0].caption.max_new_tokens': [256, undefined],
|
||||
},
|
||||
name_or_path_options: [
|
||||
{ value: 'ostris/Qwen3-Omni-30B-A3B-Instruct', label: 'ostris/Qwen3-Omni-30B-A3B-Instruct' },
|
||||
],
|
||||
additionalSections: [
|
||||
'caption.caption_prompt',
|
||||
'caption.max_res',
|
||||
'caption.max_new_tokens',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Ideogram4Captioner',
|
||||
label: 'Ideogram 4 Captioner',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import path from 'path';
|
||||
import prisma from '@/server/prisma';
|
||||
import { defaultDatasetsFolder, defaultDataRoot } from '@/paths';
|
||||
import { defaultTrainFolder } from '@/paths';
|
||||
|
|
@ -24,6 +25,9 @@ export const getDatasetsRoot = async () => {
|
|||
if (row?.value && row.value !== '') {
|
||||
datasetsPath = row.value;
|
||||
}
|
||||
// Strip trailing slashes; the routes' `root + path.sep` prefix checks 403
|
||||
// on every file if the stored path ends with a separator.
|
||||
datasetsPath = path.resolve(datasetsPath);
|
||||
myCache.set(key, datasetsPath);
|
||||
return datasetsPath as string;
|
||||
};
|
||||
|
|
@ -43,6 +47,7 @@ export const getTrainingFolder = async () => {
|
|||
if (row?.value && row.value !== '') {
|
||||
trainingRoot = row.value;
|
||||
}
|
||||
trainingRoot = path.resolve(trainingRoot);
|
||||
myCache.set(key, trainingRoot);
|
||||
return trainingRoot as string;
|
||||
};
|
||||
|
|
@ -81,6 +86,7 @@ export const getDataRoot = async () => {
|
|||
if (row?.value && row.value !== '') {
|
||||
dataRoot = row.value;
|
||||
}
|
||||
dataRoot = path.resolve(dataRoot);
|
||||
myCache.set(key, dataRoot);
|
||||
return dataRoot;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue