PageSourceSearch

https://theofficeai.net/

html theofficeai.net collected 2026-10-04 14:31:09 UTC 8,775 bytes, 294 lines download raw bytes

1<!doctype html>
2<html lang="vi">
3
4<head>
5  <meta charset="UTF-8" />
6  <link rel="icon" type="image/png" href="/favicon.png" />
7  <meta name="generator" content="Hostinger Horizons" />
8  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9  <meta name="theme-color" content="#F6F0E4" />
10  <meta name="description" content="The Office AI – Khóa học ứng dụng AI tạo sinh cho dân văn phòng. Thực tế, dễ hiểu và áp dụng được ngay vào công việc hàng ngày." />
11  <meta property="og:title" content="The Office AI – Ứng dụng AI cho dân văn phòng" />
12  <meta property="og:description" content="Khóa học AI thực tế, dễ hiểu, áp dụng ngay vào công việc văn phòng hàng ngày." />
13  <meta property="og:image" content="/favicon.png" />
14  <meta property="og:type" content="website" />
15  <meta property="og:locale" content="vi_VN" />
16  <meta name="twitter:card" content="summary_large_image" />
17  <meta name="twitter:title" content="The Office AI – Ứng dụng AI cho dân văn phòng" />
18  <meta name="twitter:description" content="Khóa học AI thực tế, dễ hiểu, áp dụng ngay vào công việc văn phòng hàng ngày." />
19  <title>The Office AI – Ứng dụng AI cho dân văn phòng</title>
20  
20<script type="module" crossorigin src="/assets/index-BIhNcnF8.js"></script>
20
21  <link rel="stylesheet" crossorigin href="/assets/index-CpdmIJsl.css">
22  
22<script type="module">
23window.onerror = (message, source, lineno, colno, errorObj) => {
24	const errorDetails = errorObj ? JSON.stringify({
25		name: errorObj.name,
26		message: errorObj.message,
27		stack: errorObj.stack,
28		source,
29		lineno,
30		colno,
31	}) : null;
32
33	window.parent.postMessage({
34		type: 'horizons-runtime-error',
35		message,
36		error: errorDetails
37	}, '*');
38};
39</script>
39
40  
40<script type="module">
41const observer = new MutationObserver((mutations) => {
42	for (const mutation of mutations) {
43		for (const addedNode of mutation.addedNodes) {
44			if (
45				addedNode.nodeType === Node.ELEMENT_NODE &&
46				(
47					addedNode.tagName?.toLowerCase() === 'vite-error-overlay' ||
48					addedNode.classList?.contains('backdrop')
49				)
50			) {
51				handleViteOverlay(addedNode);
52			}
53		}
54	}
55});
56
57observer.observe(document.documentElement, {
58	childList: true,
59	subtree: true
60});
61
62function handleViteOverlay(node) {
63	if (!node.shadowRoot) {
64		return;
65	}
66
67	const backdrop = node.shadowRoot.querySelector('.backdrop');
68
69	if (backdrop) {
70		const overlayHtml = backdrop.outerHTML;
71		const parser = new DOMParser();
72		const doc = parser.parseFromString(overlayHtml, 'text/html');
73		const messageBodyElement = doc.querySelector('.message-body');
74		const fileElement = doc.querySelector('.file');
75		const messageText = messageBodyElement ? messageBodyElement.textContent.trim() : '';
76		const fileText = fileElement ? fileElement.textContent.trim() : '';
77		const error = messageText + (fileText ? ' File:' + fileText : '');
78
79		window.parent.postMessage({
80			type: 'horizons-vite-error',
81			error,
82		}, '*');
83	}
84}
85</script>
85
86  
86<script type="module">
87const originalConsoleError = console.error;
88const MATCH_LINE_COL_REGEX = /:(\d+):(\d+)\)?\s*$/; // regex to match the :lineNum:colNum
89const MATCH_AT_REGEX = /^\s*at\s+(?:async\s+)?(?:.*?\s+)?\(?/; // regex to remove the 'at' keyword and any 'async' or function name
90const MATCH_PATH_REGEX = /^\//; // regex to remove the leading slash
91
92function parseStackFrameLine(line) {
93	const lineColMatch = line.match(MATCH_LINE_COL_REGEX);
94	if (!lineColMatch) return null;
95	const [, lineNum, colNum] = lineColMatch;
96	const suffix = `:${lineNum}:${colNum}`;
97	const idx = line.lastIndexOf(suffix);
98	if (idx === -1) return null;
99	const before = line.substring(0, idx);
100	const path = before.replace(MATCH_AT_REGEX, '').trim();
101	
102	if (!path) return null;
103
104	try {
105		const pathname = new URL(path).pathname;
106		const filePath = pathname.replace(MATCH_PATH_REGEX, '') || pathname;
107		return `${filePath}:${lineNum}:${colNum}`;
108	} catch (e) {
109		const filePath = path.replace(MATCH_PATH_REGEX, '') || path;
110		return `${filePath}:${lineNum}:${colNum}`;
111	}
112}
113
114function getFilePathFromStack(stack, skipFrames = 0) {
115	if (!stack || typeof stack !== 'string') return null;
116	const lines = stack.split('\n').slice(1);
117
118	const frames = lines.map(line => parseStackFrameLine(line.replace(/\r$/, ''))).filter(Boolean);
119
120	return frames[skipFrames] ?? null;
121}
122
123function formatConsoleMessage(args, skipStackFrames = 1) {
124	let messageString = '';
125	let filePath = null;
126
127	for (let i = 0; i < args.length; i++) {
128		const arg = args[i];
129		if (arg instanceof Error) {
130			filePath = getFilePathFromStack(arg.stack, 0);
131			messageString = `${arg.name}: ${arg.message}`;
132			if (filePath) {
133				messageString = `${messageString} at ${filePath}`;
134			}
135			break;
136		}
137	}
138
139	if (!messageString) {
140		messageString = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
141		const stack = new Error().stack;
142		filePath = getFilePathFromStack(stack, skipStackFrames);
143		if (filePath) {
144			messageString = `${messageString} at ${filePath}`;
145		}
146	}
147
148	return messageString;
149}
150
151console.error = function(...args) {
152	originalConsoleError.apply(console, args);
153
154	window.parent.postMessage({
155		type: 'horizons-console-error',
156		error: formatConsoleMessage(args, 1)
157	}, '*');
158};
159
160const originalConsoleWarn = console.warn;
161
162console.warn = function(...args) {
163	originalConsoleWarn.apply(console, args);
164
165	window.parent.postMessage({
166		type: 'horizons-console-warn',
167		warning: formatConsoleMessage(args, 1)
168	}, '*');
169};
170</script>
170
171  
171<script type="module">
172// Prevents authentication failures (PB returns 400) from crashing the app — use console.info, not console.error/PM2.
173// [urlPattern, bodyPattern] tuples; align with sandboxErrorUtils ERROR_BLACKLIST.
174const BENIGN_FETCH_ERRORS = [
175	[/hcgi\/platform\/api\/collections\/.*auth-with-password.*/i, /Failed to authenticate/i],
176	[/hcgi\/api\//i, /Insufficient credits/i],
177];
178
179function isBenignFetchError(url, body) {
180	return BENIGN_FETCH_ERRORS.some(([urlPattern, bodyPattern]) =>
181		urlPattern.test(url) && (!bodyPattern || bodyPattern.test(body)));
182}
183
184const PLATFORM_URL_PATTERN = /hcgi\/platform\//i;
185const VALIDATION_CODE_TEXT_PATTERN = /validation_/;
186
187function hasValidationCode(value) {
188	if (value == null) {
189		return false;
190	}
191	if (typeof value === 'string') {
192		return value.startsWith('validation_');
193	}
194	if (Array.isArray(value)) {
195		return value.some(hasValidationCode);
196	}
197	if (typeof value === 'object') {
198		return Object.values(value).some(hasValidationCode);
199	}
200	return false;
201}
202
203function isValidationFetchWarning(url, body) {
204	if (!PLATFORM_URL_PATTERN.test(url)) {
205		return false;
206	}
207	try {
208		return hasValidationCode(JSON.parse(body));
209	} catch {
210		return VALIDATION_CODE_TEXT_PATTERN.test(body);
211	}
212}
213
214const originalFetch = window.fetch;
215
216window.fetch = function(...args) {
217	const url = args[0] instanceof Request ? args[0].url : args[0];
218
219	// Skip WebSocket URLs
220	if (url.startsWith('ws:') || url.startsWith('wss:')) {
221		return originalFetch.apply(this, args);
222	}
223
224	return originalFetch.apply(this, args)
225		.then(async response => {
226			const contentType = response.headers.get('Content-Type') || '';
227
228			// Exclude HTML document responses
229			const isDocumentResponse =
230				contentType.includes('text/html') ||
231				contentType.includes('application/xhtml+xml');
232
233			if (!response.ok && !isDocumentResponse) {
234					const responseClone = response.clone();
235					const errorFromRes = await responseClone.text();
236					const requestUrl = response.url;
237					const errorMessage = `Fetch error from ${requestUrl}: ${errorFromRes}`;
238
239					if (isBenignFetchError(requestUrl, errorFromRes)) {
240						console.info(errorMessage);
241					} else if (isValidationFetchWarning(requestUrl, errorFromRes)) {
242						console.warn(errorMessage);
243					} else {
244						console.error(errorMessage);
245					}
246			}
247
248			return response;
249		})
250		.catch(error => {
251			if (!url.match(/.html?$/i)) {
252				// Cancelled in-flight fetch (e.g. preview reload) — not a server failure.
253				if (error?.name === 'AbortError') {
254					console.info(error);
255				} else {
256					console.error(error);
257				}
258			}
259
260			throw error;
261		});
262};
263</script>
263
264  
264<script type="module">
265if (window.navigation && window.self !== window.top) {
266	window.navigation.addEventListener('navigate', (event) => {
267		const url = event.destination.url;
268
269		try {
270			const destinationUrl = new URL(url);
271			const destinationOrigin = destinationUrl.origin;
272			const currentOrigin = window.location.origin;
273
274			if (destinationOrigin === currentOrigin) {
275				return;
276			}
277		} catch (error) {
278			return;
279		}
280
281		window.parent.postMessage({
282			type: 'horizons-navigation-error',
283			url,
284		}, '*');
285	});
286}
287</script>
287
288</head>
289
290<body>
291  <div id="root"></div>
292</body>
293
294</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.