feat: update main chat and system chat UI

This commit is contained in:
2026-05-25 17:26:37 +09:00
parent fb5ec649cd
commit f59522ffc4
120 changed files with 43262 additions and 3325 deletions

View File

@@ -2,11 +2,14 @@ const PREVIEW_RUNTIME_QUERY_KEY = 'appPreviewMode';
const PREVIEW_RUNTIME_PARENT_ORIGIN_KEY = 'previewParentOrigin';
const PREVIEW_RUNTIME_TOKEN_QUERY_KEY = 'registeredAccessToken';
const PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY = 'previewDeviceMode';
const PREVIEW_RUNTIME_CONSOLE_BRIDGE_EVENT = 'sm-home.preview-runtime.console';
const PREVIEW_TARGET_TYPE_QUERY_KEY = 'previewTargetType';
const PREVIEW_TARGET_COMPONENT_ID_QUERY_KEY = 'previewComponentId';
const PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY = 'previewSampleId';
const PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY = 'work-app.preview-runtime.cache-reset.v1';
const PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY = '__previewRuntimeCacheReset';
const PREVIEW_RUNTIME_CLEANUP_TIMEOUT_MS = 2500;
const PREVIEW_RUNTIME_NAVIGATION_GRACE_TIMEOUT_MS = 1200;
const PREVIEW_RUNTIME_PRESERVED_QUERY_KEYS = [
PREVIEW_RUNTIME_QUERY_KEY,
PREVIEW_RUNTIME_PARENT_ORIGIN_KEY,
@@ -22,6 +25,16 @@ export type PreviewTargetDescriptor =
}
| null;
export type PreviewRuntimeConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
export type PreviewRuntimeConsoleBridgeMessage = {
source: typeof PREVIEW_RUNTIME_CONSOLE_BRIDGE_EVENT;
level: PreviewRuntimeConsoleLevel;
args: string[];
timestamp: string;
href: string;
};
export function isPreviewRuntime() {
if (typeof window === 'undefined') {
return false;
@@ -116,8 +129,17 @@ async function clearPreviewRuntimeServiceWorkersAndCaches() {
return changed;
}
async function withTimeout<T>(task: Promise<T>, timeoutMs: number, fallbackValue: T) {
return await Promise.race([
task,
new Promise<T>((resolve) => {
window.setTimeout(() => resolve(fallbackValue), timeoutMs);
}),
]);
}
export async function ensurePreviewRuntimeFreshState() {
if (typeof window === 'undefined' || (!isPreviewRuntime() && !isPreviewAppOrigin())) {
if (typeof window === 'undefined' || !isPreviewRuntime()) {
return;
}
@@ -129,7 +151,7 @@ export async function ensurePreviewRuntimeFreshState() {
return;
}
const changed = await clearPreviewRuntimeServiceWorkersAndCaches();
const changed = await withTimeout(clearPreviewRuntimeServiceWorkersAndCaches(), PREVIEW_RUNTIME_CLEANUP_TIMEOUT_MS, false);
if (!changed) {
if (resetSearchParam) {
@@ -144,8 +166,8 @@ export async function ensurePreviewRuntimeFreshState() {
writePreviewRuntimeCacheResetMarker(currentLocationKey);
window.location.replace(buildPreviewRuntimeCacheResetUrl());
await new Promise(() => {
// Keep the bootstrap suspended until the browser navigates away.
await new Promise<void>((resolve) => {
window.setTimeout(resolve, PREVIEW_RUNTIME_NAVIGATION_GRACE_TIMEOUT_MS);
});
}
@@ -157,6 +179,148 @@ export function getPreviewRuntimeParentOrigin() {
return new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_PARENT_ORIGIN_KEY)?.trim() ?? '';
}
function stringifyPreviewRuntimeConsoleArg(value: unknown, seen = new WeakSet<object>()): string {
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
return String(value);
}
if (typeof value === 'undefined') {
return 'undefined';
}
if (value === null) {
return 'null';
}
if (typeof value === 'function') {
return `[function ${value.name || 'anonymous'}]`;
}
if (value instanceof Error) {
return value.stack || `${value.name}: ${value.message}`;
}
if (value instanceof URL) {
return value.toString();
}
if (typeof value === 'object') {
if (seen.has(value)) {
return '[circular]';
}
seen.add(value);
try {
return JSON.stringify(
value,
(_key, nestedValue) => {
if (typeof nestedValue === 'bigint') {
return nestedValue.toString();
}
if (nestedValue instanceof Error) {
return {
name: nestedValue.name,
message: nestedValue.message,
stack: nestedValue.stack,
};
}
if (nestedValue instanceof URL) {
return nestedValue.toString();
}
if (typeof nestedValue === 'function') {
return `[function ${nestedValue.name || 'anonymous'}]`;
}
if (nestedValue && typeof nestedValue === 'object') {
if (seen.has(nestedValue)) {
return '[circular]';
}
seen.add(nestedValue);
}
return nestedValue;
},
2,
);
} catch {
return Object.prototype.toString.call(value);
}
}
return String(value);
}
function postPreviewRuntimeConsoleMessage(level: PreviewRuntimeConsoleLevel, args: unknown[]) {
if (typeof window === 'undefined') {
return;
}
const parentOrigin = getPreviewRuntimeParentOrigin();
if (!parentOrigin || window.parent === window) {
return;
}
const payload: PreviewRuntimeConsoleBridgeMessage = {
source: PREVIEW_RUNTIME_CONSOLE_BRIDGE_EVENT,
level,
args: args.map((arg) => stringifyPreviewRuntimeConsoleArg(arg)),
timestamp: new Date().toISOString(),
href: window.location.href,
};
window.parent.postMessage(payload, parentOrigin);
}
export function installPreviewRuntimeConsoleBridge() {
if (typeof window === 'undefined' || !isPreviewRuntime()) {
return;
}
const consoleMethods: PreviewRuntimeConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug'];
const previewConsole = window.console as Console & {
__smHomePreviewConsoleBridgeInstalled?: boolean;
};
if (previewConsole.__smHomePreviewConsoleBridgeInstalled) {
return;
}
previewConsole.__smHomePreviewConsoleBridgeInstalled = true;
consoleMethods.forEach((level) => {
const originalMethod = previewConsole[level].bind(previewConsole);
previewConsole[level] = (...args: unknown[]) => {
postPreviewRuntimeConsoleMessage(level, args);
originalMethod(...args);
};
});
window.addEventListener('error', (event) => {
postPreviewRuntimeConsoleMessage('error', [
event.message || 'Unknown error',
event.filename ? `${event.filename}:${event.lineno}:${event.colno}` : '',
event.error ?? '',
]);
});
window.addEventListener('unhandledrejection', (event) => {
postPreviewRuntimeConsoleMessage('error', ['Unhandled promise rejection', event.reason ?? '']);
});
postPreviewRuntimeConsoleMessage('info', ['Preview runtime console bridge connected']);
}
export function readPreviewRuntimeDeviceModeFromUrl(): 'desktop' | 'mobile' | null {
if (typeof window === 'undefined') {
return null;