feat: implement channel runtime connectors
This commit is contained in:
@ -10,6 +10,7 @@ import type { ChatLogEvent, ChatLogSession } from '@/types';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { containedJsonTextClass } from '@/lib/text-wrapping';
|
||||
|
||||
function eventLabel(event: ChatLogEvent): string {
|
||||
return event.event_type || event.role || 'event';
|
||||
@ -175,7 +176,7 @@ export default function LogsPage() {
|
||||
return (
|
||||
<div
|
||||
key={`${event.message_id ?? index}:${event.event_type}`}
|
||||
className="rounded-lg border border-border bg-background"
|
||||
className="min-w-0 max-w-full overflow-hidden rounded-lg border border-border bg-background"
|
||||
>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b px-3 py-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
@ -188,7 +189,7 @@ export default function LogsPage() {
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">{timestampLabel(event.timestamp)}</span>
|
||||
</div>
|
||||
<pre className="max-h-[520px] overflow-auto whitespace-pre-wrap break-words p-3 text-xs leading-5 text-foreground">
|
||||
<pre className={`max-h-[520px] overflow-auto p-3 text-xs leading-5 text-foreground ${containedJsonTextClass}`}>
|
||||
{body || formatPayload(event)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@ -19,7 +19,12 @@ import {
|
||||
uploadFile,
|
||||
wsManager,
|
||||
} from '@/lib/api';
|
||||
import { mergeServerWithPendingUsers, shouldDisplayChatMessage, shouldMergePendingUsers } from '@/lib/chat-messages';
|
||||
import {
|
||||
getSessionRefreshIntervalMs,
|
||||
mergeServerWithPendingUsers,
|
||||
shouldDisplayChatMessage,
|
||||
shouldMergePendingUsers,
|
||||
} from '@/lib/chat-messages';
|
||||
import { pickAppText } from '@/lib/i18n/core';
|
||||
import { useAppI18n } from '@/lib/i18n/provider';
|
||||
import { buildSessionProgressView } from '@/lib/session-progress';
|
||||
@ -47,6 +52,10 @@ function loadThinkingModePreference(): boolean {
|
||||
return stored == null ? false : stored !== 'false';
|
||||
}
|
||||
|
||||
function isDocumentHidden(): boolean {
|
||||
return typeof document !== 'undefined' && document.visibilityState === 'hidden';
|
||||
}
|
||||
|
||||
export default function ChatPage() {
|
||||
const { locale } = useAppI18n();
|
||||
const {
|
||||
@ -78,6 +87,7 @@ export default function ChatPage() {
|
||||
const [pendingFiles, setPendingFiles] = useState<Array<{ file: File; id?: string; progress: number; error?: string }>>([]);
|
||||
const [activeTask, setActiveTask] = useState<ActiveTask | null>(null);
|
||||
const [revisionTargetRunId, setRevisionTargetRunId] = useState<string | null>(null);
|
||||
const [documentHidden, setDocumentHidden] = useState(isDocumentHidden);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const messageViewportRef = useRef<HTMLDivElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
@ -247,14 +257,26 @@ export default function ChatPage() {
|
||||
}, [addMessage, loadActiveTask, loadSessionMessages, loadSessions, setIsLoading, setIsThinking]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !isThinking) {
|
||||
const intervalMs = getSessionRefreshIntervalMs({ isLoading, isThinking, documentHidden });
|
||||
if (intervalMs == null) {
|
||||
return;
|
||||
}
|
||||
const timer = setInterval(() => {
|
||||
loadSessionMessages(useChatStore.getState().sessionId);
|
||||
}, 1500);
|
||||
const currentSessionId = useChatStore.getState().sessionId;
|
||||
void loadSessionMessages(currentSessionId);
|
||||
void loadSessions();
|
||||
}, intervalMs);
|
||||
return () => clearInterval(timer);
|
||||
}, [isLoading, isThinking, loadSessionMessages]);
|
||||
}, [documentHidden, isLoading, isThinking, loadSessionMessages, loadSessions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const updateVisibility = () => setDocumentHidden(isDocumentHidden());
|
||||
document.addEventListener('visibilitychange', updateVisibility);
|
||||
return () => document.removeEventListener('visibilitychange', updateVisibility);
|
||||
}, []);
|
||||
|
||||
const scrollMessagesToLatest = useCallback((behavior: ScrollBehavior) => {
|
||||
const viewport = messageViewportRef.current;
|
||||
|
||||
@ -73,6 +73,7 @@ import type {
|
||||
} from '@/types';
|
||||
import { pickAppText } from '@/lib/i18n/core';
|
||||
import { useAppI18n } from '@/lib/i18n/provider';
|
||||
import { containedJsonTextClass, containedLongTextClass } from '@/lib/text-wrapping';
|
||||
|
||||
const TERMINAL_DRAFT_STATUSES = new Set(['rejected', 'published', 'disabled', 'archived']);
|
||||
const REJECTABLE_DRAFT_STATUSES = new Set(['draft', 'in_review', 'approved']);
|
||||
@ -1094,7 +1095,7 @@ function ReadableFact({
|
||||
{icon}
|
||||
{label}
|
||||
</div>
|
||||
<div className="break-words text-sm leading-5">{value || '-'}</div>
|
||||
<div className={`text-sm leading-5 ${containedLongTextClass}`}>{value || '-'}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1119,12 +1120,12 @@ function MetricTile({
|
||||
|
||||
function RawDetails({ title, payload }: { title: string; payload: unknown }) {
|
||||
return (
|
||||
<details className="mt-3 rounded-md border border-border bg-white">
|
||||
<details className="mt-3 min-w-0 max-w-full overflow-hidden rounded-md border border-border bg-white">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-medium text-muted-foreground">
|
||||
{title}
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</summary>
|
||||
<pre className="max-h-72 overflow-auto border-t border-border p-3 text-xs leading-5">
|
||||
<pre className={`max-h-72 overflow-auto border-t border-border p-3 text-xs leading-5 ${containedJsonTextClass}`}>
|
||||
{JSON.stringify(payload, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -88,6 +88,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.contained-long-text {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.contained-preserved-long-text {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.contained-json-text {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
}
|
||||
|
||||
/* Override Tailwind Typography table defaults for markdown rendering */
|
||||
.prose table {
|
||||
margin-top: 0;
|
||||
|
||||
Reference in New Issue
Block a user