mirror of
https://github.com/danielmiessler/fabric.git
synced 2026-09-10 07:36:44 -04:00
feat: WebUI: inline YouTube transcripts during chat flow
- Replace YouTube URLs with fetched transcript blocks - Support multiple YouTube links inside one message - Always render user message before processing response - Show YouTube-specific loading indicator during transcript fetch - Reset YouTube URL state after submission processing - Upgrade SvelteKit, Svelte, Tailwind, TypeScript toolchain versions - Bump pdfjs-dist and @napi-rs/canvas optional dependency versions - Refresh esbuild binaries and Floating UI runtime versions - Regenerate pnpm/npm lockfiles with updated transitive dependencies
This commit is contained in:
parent
c6789bedf7
commit
34bd2ece1e
911
web/package-lock.json
generated
911
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -16,35 +16,35 @@
|
|||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.27.0",
|
||||
"@eslint/js": "^9.39.3",
|
||||
"@skeletonlabs/skeleton": "^2.11.0",
|
||||
"@skeletonlabs/tw-plugin": "^0.3.1",
|
||||
"@sveltejs/adapter-auto": "^3.3.1",
|
||||
"@sveltejs/kit": "^2.53.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^20.17.50",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"@sveltejs/kit": "^2.53.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.4",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/node": "^20.19.35",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"lucide-svelte": "^0.575.0",
|
||||
"mdsvex": "^0.11.2",
|
||||
"patch-package": "^8.0.1",
|
||||
"pdf-to-markdown-core": "github:jzillmann/pdf-to-markdown#modularize",
|
||||
"pdfjs-dist": "^5.4.449",
|
||||
"postcss": "^8.5.3",
|
||||
"pdfjs-dist": "^5.4.624",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"shiki": "^1.29.2",
|
||||
"svelte": "^5.53.5",
|
||||
"svelte": "^5.53.6",
|
||||
"svelte-check": "^3.8.6",
|
||||
"svelte-inview": "^4.0.4",
|
||||
"svelte-reveal": "^1.1.0",
|
||||
"svelte-youtube-embed": "^0.3.3",
|
||||
"svelte-youtube-lite": "^0.6.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^5.4.21",
|
||||
"vite-plugin-tailwind-purgecss": "^0.2.1"
|
||||
},
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
"@eslint/plugin-kit": ">=0.3.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.7.0",
|
||||
"@floating-ui/dom": "^1.7.5",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
|
|
@ -74,9 +74,9 @@
|
|||
"rehype": "^13.0.2",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"rehype-unwrap-images": "^1.0.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"vfile-message": "^4.0.2",
|
||||
"yaml": "^2.8.0",
|
||||
"tailwind-merge": "^2.6.1",
|
||||
"vfile-message": "^4.0.3",
|
||||
"yaml": "^2.8.2",
|
||||
"youtube-transcript": "^1.2.1"
|
||||
},
|
||||
"pnpm": {
|
||||
|
|
|
|||
1022
web/pnpm-lock.yaml
1022
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -306,78 +306,21 @@ async function readFileContent(file: File): Promise<string> {
|
|||
}
|
||||
|
||||
// Centralized language instruction logic in ChatService.ts; YouTube flow now passes plain transcript and system prompt
|
||||
async function processYouTubeURL(input: string) {
|
||||
console.log('\n=== YouTube Flow Start ===');
|
||||
const originalLanguage = get(languageStore);
|
||||
function extractYouTubeURLs(input: string): string[] {
|
||||
const youtubePattern = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=[\w-]+(?:&[^\s]*)?|youtu\.be\/[\w-]+(?:\?[^\s]*)?)/gi;
|
||||
return input.match(youtubePattern) || [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Add processing message first
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing YouTube video...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
async function replaceYouTubeURLsWithTranscripts(input: string): Promise<string> {
|
||||
const urls = extractYouTubeURLs(input);
|
||||
if (urls.length === 0) return input;
|
||||
|
||||
// Get transcript but don't display it
|
||||
const { transcript } = await getTranscript(input);
|
||||
|
||||
// Pass plain transcript and system prompt; ChatService will handle language instruction
|
||||
const stream = await chatService.streamChat(transcript, $systemPrompt);
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content, response) => {
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
// Replace the processing message with actual content
|
||||
const loadingIndex = newMessages.findIndex(m => m.format === 'loading');
|
||||
if (loadingIndex !== -1) {
|
||||
newMessages.splice(loadingIndex, 1);
|
||||
}
|
||||
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastMessage.content += content;
|
||||
lastMessage.format = response?.format;
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
// Handle Obsidian saving if needed
|
||||
if ($obsidianSettings.saveToObsidian) {
|
||||
let lastContent = '';
|
||||
messageStore.subscribe(messages => {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastContent = lastMessage.content;
|
||||
}
|
||||
})();
|
||||
if (lastContent) await saveToObsidian(lastContent);
|
||||
}
|
||||
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
} catch (error) {
|
||||
console.error('Error processing YouTube URL:', error);
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
throw error;
|
||||
let result = input;
|
||||
for (const url of urls) {
|
||||
const { transcript } = await getTranscript(url);
|
||||
result = result.replace(url, `[YouTube Transcript]:\n${transcript}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
|
|
@ -385,46 +328,48 @@ async function readFileContent(file: File): Promise<string> {
|
|||
|
||||
try {
|
||||
console.log('\n=== Submit Handler Start ===');
|
||||
|
||||
|
||||
// Store the user input before any processing
|
||||
const inputText = userInput.trim();
|
||||
console.log('Captured user input:', inputText);
|
||||
|
||||
// Handle YouTube URLs with the existing flow
|
||||
if (isYouTubeURL) {
|
||||
console.log('2a. Starting YouTube flow');
|
||||
await processYouTubeURL(inputText);
|
||||
return;
|
||||
}
|
||||
|
||||
// For regular text input, add the user message to the UI first
|
||||
|
||||
// Add the user message to the UI first
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'user',
|
||||
content: inputText
|
||||
}]);
|
||||
|
||||
|
||||
// Add loading indicator
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing...',
|
||||
content: isYouTubeURL ? 'Processing YouTube video...' : 'Processing...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
|
||||
// Clear input fields
|
||||
userInput = "";
|
||||
const hadYouTubeURL = isYouTubeURL;
|
||||
isYouTubeURL = false;
|
||||
const filesForProcessing = [...uploadedFiles];
|
||||
const contentsForProcessing = [...fileContents];
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
fileButtonKey = !fileButtonKey;
|
||||
|
||||
|
||||
// If the message contains YouTube URLs, replace them with transcripts
|
||||
let processedText = inputText;
|
||||
if (hadYouTubeURL) {
|
||||
console.log('Replacing YouTube URLs with transcripts');
|
||||
processedText = await replaceYouTubeURLsWithTranscripts(inputText);
|
||||
}
|
||||
|
||||
// Prepare content with file attachments if any
|
||||
const contentWithFiles = contentsForProcessing.length > 0
|
||||
? `${inputText}\n\nFile Contents (${filesForProcessing.map(f => f.endsWith('.pdf') ? 'PDF' : 'Text').join(', ')}):\n${contentsForProcessing.join('\n\n---\n\n')}`
|
||||
: inputText;
|
||||
|
||||
const contentWithFiles = contentsForProcessing.length > 0
|
||||
? `${processedText}\n\nFile Contents (${filesForProcessing.map(f => f.endsWith('.pdf') ? 'PDF' : 'Text').join(', ')}):\n${contentsForProcessing.join('\n\n---\n\n')}`
|
||||
: processedText;
|
||||
|
||||
// Get the enhanced prompt
|
||||
const enhancedPrompt = contentsForProcessing.length > 0
|
||||
const enhancedPrompt = contentsForProcessing.length > 0
|
||||
? `${$systemPrompt}\nAnalyze and process the provided content according to these instructions.`
|
||||
: $systemPrompt;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue