Studio: make figure captioning optional with a Retrieval toggle
This commit is contained in:
parent
c149cf58d1
commit
7335dc07a9
10 changed files with 101 additions and 23 deletions
|
|
@ -39,6 +39,7 @@ export interface PersistedChatSettings {
|
|||
ragTopK?: number;
|
||||
ragMinScore?: number;
|
||||
ragIndexConcurrency?: number;
|
||||
ragCaptionImages?: boolean;
|
||||
}
|
||||
|
||||
interface ChatSettingsResponse {
|
||||
|
|
|
|||
|
|
@ -500,6 +500,10 @@ export function ChatSettingsPanel({
|
|||
const setRagIndexConcurrency = useChatRuntimeStore(
|
||||
(s) => s.setRagIndexConcurrency,
|
||||
);
|
||||
const ragCaptionImages = useChatRuntimeStore((s) => s.ragCaptionImages);
|
||||
const setRagCaptionImages = useChatRuntimeStore(
|
||||
(s) => s.setRagCaptionImages,
|
||||
);
|
||||
const activeThreadId = useChatRuntimeStore((s) => s.activeThreadId);
|
||||
const { knowledgeBases, deleteKB } = useKnowledgeBases();
|
||||
const { documents: threadDocs, remove: removeThreadDoc } = useThreadDocuments(
|
||||
|
|
@ -573,7 +577,10 @@ export function ChatSettingsPanel({
|
|||
`with the new settings? Existing chunks will be deleted and rebuilt.`,
|
||||
);
|
||||
if (ok) {
|
||||
void reingestThread(threadId, patch);
|
||||
void reingestThread(threadId, {
|
||||
...patch,
|
||||
caption_images: ragCaptionImages,
|
||||
});
|
||||
} else {
|
||||
// User declined: refresh so the select snaps back.
|
||||
void loadThreadSettings(threadId);
|
||||
|
|
@ -1466,6 +1473,22 @@ export function ChatSettingsPanel({
|
|||
source before sending.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[13px] font-medium">
|
||||
Caption images
|
||||
</span>
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
Describe figures with a vision model during indexing so
|
||||
they're searchable. Off = faster, text-only indexing.
|
||||
</span>
|
||||
</div>
|
||||
<Switch
|
||||
checked={ragCaptionImages}
|
||||
onCheckedChange={(next) => setRagCaptionImages(next)}
|
||||
disabled={!ragEnabled}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-[12px] font-medium text-muted-foreground">
|
||||
Search mode
|
||||
|
|
@ -1611,7 +1634,9 @@ export function ChatSettingsPanel({
|
|||
`Re-index all ${threadDocs.length} document${threadDocs.length === 1 ? "" : "s"}? Existing chunks will be deleted and rebuilt; search will be unavailable until ingestion finishes.`,
|
||||
)
|
||||
) {
|
||||
void reingestThread(activeThreadId);
|
||||
void reingestThread(activeThreadId, {
|
||||
caption_images: ragCaptionImages,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -188,12 +188,14 @@ export function useThreadDocUploads(): UseThreadDocUploadsResult {
|
|||
}
|
||||
setChipScopeKeys((m) => ({ ...m, [localChipId]: scopeKey as string }));
|
||||
const uploadDocument = useRagStore.getState().uploadDocument;
|
||||
const captionImages =
|
||||
useChatRuntimeStore.getState().ragCaptionImages;
|
||||
try {
|
||||
const {
|
||||
documentId: did,
|
||||
jobId: jid,
|
||||
alreadyIndexed,
|
||||
} = await uploadDocument(scope, file);
|
||||
} = await uploadDocument(scope, file, captionImages);
|
||||
documentId = did;
|
||||
jobId = jid;
|
||||
if (abort.signal.aborted) {
|
||||
|
|
|
|||
|
|
@ -679,12 +679,14 @@ export function SharedComposer({
|
|||
scopeKey = `thread:${threadId}`;
|
||||
}
|
||||
const uploadDocument = useRagStore.getState().uploadDocument;
|
||||
const captionImages =
|
||||
useChatRuntimeStore.getState().ragCaptionImages;
|
||||
try {
|
||||
const {
|
||||
documentId: did,
|
||||
jobId: jid,
|
||||
alreadyIndexed,
|
||||
} = await uploadDocument(scope, file);
|
||||
} = await uploadDocument(scope, file, captionImages);
|
||||
documentId = did;
|
||||
jobId = jid;
|
||||
if (abort.signal.aborted) {
|
||||
|
|
|
|||
|
|
@ -334,6 +334,9 @@ type ChatRuntimeStore = {
|
|||
// rate). 1 = sequential. Keeps many concurrent ingestion subprocesses
|
||||
// from thrashing the GPU/CPU.
|
||||
ragIndexConcurrency: number;
|
||||
// Caption figures/images during ingestion (default on). Off skips the VLM
|
||||
// captioning pass for faster, text-only indexing.
|
||||
ragCaptionImages: boolean;
|
||||
hydratePersistedSettings: () => Promise<void>;
|
||||
setModelLoading: (loading: boolean) => void;
|
||||
setModelRequiresTrustRemoteCode: (required: boolean) => void;
|
||||
|
|
@ -389,6 +392,7 @@ type ChatRuntimeStore = {
|
|||
setRagTopK: (value: number) => void;
|
||||
setRagMinScore: (value: number) => void;
|
||||
setRagIndexConcurrency: (value: number) => void;
|
||||
setRagCaptionImages: (value: boolean) => void;
|
||||
setRagToolEnabled: (value: boolean) => void;
|
||||
};
|
||||
|
||||
|
|
@ -411,7 +415,8 @@ type ScalarSettingKey =
|
|||
| "enableRerank"
|
||||
| "ragTopK"
|
||||
| "ragMinScore"
|
||||
| "ragIndexConcurrency";
|
||||
| "ragIndexConcurrency"
|
||||
| "ragCaptionImages";
|
||||
|
||||
type PresetHydrationVersions = {
|
||||
customPresets: number;
|
||||
|
|
@ -452,6 +457,7 @@ const SCALAR_SETTING_KEYS = [
|
|||
"ragTopK",
|
||||
"ragMinScore",
|
||||
"ragIndexConcurrency",
|
||||
"ragCaptionImages",
|
||||
] as const satisfies readonly ScalarSettingKey[];
|
||||
|
||||
const inferenceParamMutationVersions = Object.fromEntries(
|
||||
|
|
@ -668,6 +674,7 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
ragTopK: 5,
|
||||
ragMinScore: 0,
|
||||
ragIndexConcurrency: 1,
|
||||
ragCaptionImages: true,
|
||||
hydratePersistedSettings: async () => {
|
||||
if (get().settingsHydrated) {
|
||||
return;
|
||||
|
|
@ -952,6 +959,15 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
);
|
||||
return { ragIndexConcurrency: clamped };
|
||||
}),
|
||||
setRagCaptionImages: (ragCaptionImages) =>
|
||||
set((state) => {
|
||||
setScalarSettingVersion(
|
||||
"ragCaptionImages",
|
||||
ragCaptionImages,
|
||||
state.ragCaptionImages,
|
||||
);
|
||||
return { ragCaptionImages };
|
||||
}),
|
||||
setToolsEnabled: (toolsEnabled, options) =>
|
||||
set(() => {
|
||||
if (options?.persist !== false) {
|
||||
|
|
|
|||
|
|
@ -232,11 +232,12 @@ export async function listThreadDocuments(
|
|||
export async function uploadKBDocument(
|
||||
kbId: string,
|
||||
file: File,
|
||||
captionImages = true,
|
||||
): Promise<UploadResponse> {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
const response = await authFetch(
|
||||
`/api/rag/knowledge-bases/${encodeURIComponent(kbId)}/documents`,
|
||||
`/api/rag/knowledge-bases/${encodeURIComponent(kbId)}/documents?caption_images=${captionImages}`,
|
||||
{ method: "POST", body: form },
|
||||
);
|
||||
return parseJsonOrThrow<UploadResponse>(response);
|
||||
|
|
@ -245,11 +246,12 @@ export async function uploadKBDocument(
|
|||
export async function uploadThreadDocument(
|
||||
threadId: string,
|
||||
file: File,
|
||||
captionImages = true,
|
||||
): Promise<UploadResponse> {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
const response = await authFetch(
|
||||
`/api/rag/threads/${encodeURIComponent(threadId)}/documents`,
|
||||
`/api/rag/threads/${encodeURIComponent(threadId)}/documents?caption_images=${captionImages}`,
|
||||
{ method: "POST", body: form },
|
||||
);
|
||||
return parseJsonOrThrow<UploadResponse>(response);
|
||||
|
|
@ -295,6 +297,7 @@ export interface ReingestKBOptions {
|
|||
chunking_strategy?: ChunkingStrategy;
|
||||
mode?: KBMode;
|
||||
embedding_model?: string;
|
||||
caption_images?: boolean;
|
||||
}
|
||||
|
||||
export async function reingestKnowledgeBase(
|
||||
|
|
@ -322,6 +325,8 @@ export interface UpdateThreadRagSettingsRequest {
|
|||
chunking_strategy?: ChunkingStrategy;
|
||||
mode?: KBMode;
|
||||
embedding_model?: string | null;
|
||||
// Only consulted by reingest (not persisted as a thread setting).
|
||||
caption_images?: boolean;
|
||||
}
|
||||
|
||||
export async function getThreadRagSettings(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import type {
|
|||
KBMode,
|
||||
KnowledgeBase,
|
||||
} from "../api/rag-api";
|
||||
import { useChatRuntimeStore } from "@/features/chat/stores/chat-runtime-store";
|
||||
import { useRagStore } from "../stores/rag-store";
|
||||
|
||||
export function KBReconfigureDialog({
|
||||
|
|
@ -93,6 +94,7 @@ export function KBReconfigureDialog({
|
|||
chunking_strategy: chunkingStrategy,
|
||||
mode,
|
||||
embedding_model: embeddingModel.trim() || undefined,
|
||||
caption_images: useChatRuntimeStore.getState().ragCaptionImages,
|
||||
});
|
||||
onOpenChange(false);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ interface RagStoreState {
|
|||
uploadDocument: (
|
||||
scope: { kind: "kb"; kbId: string } | { kind: "thread"; threadId: string },
|
||||
file: File,
|
||||
captionImages?: boolean,
|
||||
) => Promise<{
|
||||
documentId: string;
|
||||
jobId: string;
|
||||
|
|
@ -189,11 +190,11 @@ export const useRagStore = create<RagStoreState>((set, get) => ({
|
|||
}
|
||||
},
|
||||
|
||||
async uploadDocument(scope, file) {
|
||||
async uploadDocument(scope, file, captionImages = true) {
|
||||
const result =
|
||||
scope.kind === "kb"
|
||||
? await uploadKBDocument(scope.kbId, file)
|
||||
: await uploadThreadDocument(scope.threadId, file);
|
||||
? await uploadKBDocument(scope.kbId, file, captionImages)
|
||||
: await uploadThreadDocument(scope.threadId, file, captionImages);
|
||||
const scopeKey =
|
||||
scope.kind === "kb"
|
||||
? kbScopeKey(scope.kbId)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue