add URL translation for content extraction, fetch raw over DOM scraping
for known dynamic pages (GitLab CI jobs), fetch the raw/plain text endpoint instead of scraping a half-loaded DOM. falls back to DOM extraction with a MutationObserver settle wait when raw isn't accessible. extractWebpageContent is now async, all callers updated.
This commit is contained in:
parent
1a5b576a7d
commit
4f589f2449
4 changed files with 101 additions and 8 deletions
|
|
@ -1,7 +1,73 @@
|
|||
// Content extraction and processing functionality
|
||||
|
||||
// Function to extract text content along with links and metadata from the webpage
|
||||
export function extractWebpageContent() {
|
||||
// URL translation rules — map dynamic page patterns to raw/plain text equivalents.
|
||||
// Each rule: { pattern: RegExp matching the full URL, translate: (match) => rawURL }
|
||||
const URL_TRANSLATIONS = [
|
||||
{
|
||||
// GitLab CI job pages → raw log output
|
||||
// e.g. https://git.example.com/group/project/-/jobs/12345 → .../jobs/12345/raw
|
||||
pattern: /^(https?:\/\/[^/]+\/.*\/-\/jobs\/\d+)\/?$/,
|
||||
translate: (match) => `${match[1]}/raw`,
|
||||
},
|
||||
];
|
||||
|
||||
// Try to fetch raw content via URL translation (same-origin, cookies included).
|
||||
// Returns { raw: string } on success, { matched: true } if pattern matched but
|
||||
// fetch failed (signals a dynamic page worth waiting for), or null if no match.
|
||||
async function fetchTranslatedContent(url) {
|
||||
for (const rule of URL_TRANSLATIONS) {
|
||||
const match = url.match(rule.pattern);
|
||||
if (match) {
|
||||
try {
|
||||
const rawUrl = rule.translate(match);
|
||||
const response = await fetch(rawUrl, { credentials: "same-origin" });
|
||||
if (response.ok) {
|
||||
const text = await response.text();
|
||||
if (text && text.length > 0) {
|
||||
return { raw: text };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("URL translation fetch failed, falling back to DOM:", e);
|
||||
}
|
||||
// Pattern matched but fetch failed — caller should wait for DOM to settle
|
||||
return { matched: true };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wait for DOM mutations to settle (content stops streaming in)
|
||||
function waitForDOMSettle(timeout = 3000, quiet = 500) {
|
||||
return new Promise((resolve) => {
|
||||
let timer;
|
||||
const observer = new MutationObserver(() => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
observer.disconnect();
|
||||
resolve();
|
||||
}, quiet);
|
||||
});
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
});
|
||||
// Start the quiet timer immediately in case DOM is already settled
|
||||
timer = setTimeout(() => {
|
||||
observer.disconnect();
|
||||
resolve();
|
||||
}, quiet);
|
||||
// Hard ceiling so we don't wait forever
|
||||
setTimeout(() => {
|
||||
observer.disconnect();
|
||||
resolve();
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
// Extract DOM content — title, metadata, text with links
|
||||
function extractDOMContent() {
|
||||
let content = "";
|
||||
|
||||
// Extract title
|
||||
|
|
@ -40,3 +106,30 @@ export function extractWebpageContent() {
|
|||
getTextWithLinks(document.body);
|
||||
return content.trim();
|
||||
}
|
||||
|
||||
// Extract text content along with links and metadata from the webpage.
|
||||
// Tries URL translation first (raw/plain text for known dynamic pages),
|
||||
// falls back to DOM extraction.
|
||||
export async function extractWebpageContent() {
|
||||
const result = await fetchTranslatedContent(window.location.href);
|
||||
|
||||
// Raw content fetched successfully — use it directly
|
||||
if (result?.raw) {
|
||||
let content = "";
|
||||
const title = document.title;
|
||||
if (title) {
|
||||
content += `**Page Title**: ${title}\n\n`;
|
||||
}
|
||||
content += `**Source URL**: ${window.location.href}\n\n`;
|
||||
content += result.raw;
|
||||
return content.trim();
|
||||
}
|
||||
|
||||
// Pattern matched but raw fetch failed (auth, CORS, etc) —
|
||||
// this is a known dynamic page, wait for DOM to settle before scraping
|
||||
if (result?.matched) {
|
||||
await waitForDOMSettle();
|
||||
}
|
||||
|
||||
return extractDOMContent();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { getUIText } from "./ui-translations.js";
|
|||
|
||||
// Function to read the entire page using Hermes
|
||||
export async function readPageWithHermes(button = null) {
|
||||
const content = extractWebpageContent();
|
||||
const content = await extractWebpageContent();
|
||||
|
||||
// Update button tooltip instead of creating separate DOM element
|
||||
if (button) {
|
||||
|
|
|
|||
|
|
@ -295,14 +295,14 @@ export function initializeUncloseaiElements() {
|
|||
// Widget creation functions imported from widget-library.js
|
||||
|
||||
// Initialize the legacy chat interface (for backward compatibility)
|
||||
export function initializeChatInterface() {
|
||||
export async function initializeChatInterface() {
|
||||
// Only initialize if there are legacy elements (chat-container, user-input, etc.)
|
||||
const legacyElements = document.querySelector(
|
||||
"#chat-container, #user-input, #chat-box",
|
||||
);
|
||||
if (!legacyElements) return;
|
||||
|
||||
const pageContent = extractWebpageContent();
|
||||
const pageContent = await extractWebpageContent();
|
||||
chatHistory.push({
|
||||
role: "system",
|
||||
content: `Here's the content of the webpage: ${pageContent}`,
|
||||
|
|
|
|||
|
|
@ -1177,7 +1177,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
try {
|
||||
// Get page content
|
||||
const { extractWebpageContent } = await import("./content.js");
|
||||
const pageContent = extractWebpageContent();
|
||||
const pageContent = await extractWebpageContent();
|
||||
|
||||
// Generate TTS
|
||||
const { speakText } = await import("./tts.js");
|
||||
|
|
@ -1895,7 +1895,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
console.log("History length:", history ? history.length : 0);
|
||||
if (history && history.length > 0) {
|
||||
// Set up page context for existing conversation
|
||||
const pageContent = extractWebpageContent();
|
||||
const pageContent = await extractWebpageContent();
|
||||
const pageTitle = document.title || window.location.hostname;
|
||||
const conversationContextAppend = `
|
||||
|
||||
|
|
@ -2324,7 +2324,7 @@ You have complete knowledge of this page content and can reference any details,
|
|||
|
||||
try {
|
||||
// Get page content for context
|
||||
const pageContent = extractWebpageContent();
|
||||
const pageContent = await extractWebpageContent();
|
||||
const pageTitle = document.title || window.location.hostname;
|
||||
|
||||
// FIRST: Generate intro message with specialized intro system prompt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue