diff --git a/app/globals.css b/app/globals.css index 6b97128..e417d16 100644 --- a/app/globals.css +++ b/app/globals.css @@ -6,3 +6,9 @@ from { transform: rotate(0deg); } to { transform: rotate(360deg); } } + +@media (prefers-reduced-motion: reduce) { + [style*="animation:spin"] { + animation: none !important; + } +} diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx index 6bd547e..640a9ce 100644 --- a/components/reasoning-workspace.jsx +++ b/components/reasoning-workspace.jsx @@ -214,13 +214,13 @@ function LoadingOverlay({ isLoading, elapsed, currentMessage, variant }) { } return ( -
{statusText}
-+
This has been running for {elapsed}s. {variant === "initial" && elapsed >= 45 && ( This can take around a minute with the current local model. diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx index b742c90..a3c8458 100644 --- a/components/scenario-form.jsx +++ b/components/scenario-form.jsx @@ -1,11 +1,11 @@ "use client"; import React from "react"; -import { useState, useRef } from "react"; +import { useState, useRef, useEffect, useMemo } from "react"; import DiagnosticsView from "@/components/diagnostics-view"; import GraphUpdateView from "@/components/graph-update-view"; import SituationGraphView from "@/components/situation-graph-view"; -import ReasoningWorkspace from "@/components/reasoning-workspace"; +import ReasoningWorkspace, { LoadingOverlay } from "@/components/reasoning-workspace"; const MAX_LENGTH = 10000; @@ -100,6 +100,50 @@ export function ScenarioResultPanels({ status, result }) { ); } +// ── Message pools ─────────────────────────────────────────── +const INITIAL_MESSAGES = [ + { min: 0, text: "Reading your situation" }, + { min: 10, text: "Building a structured understanding" }, + { min: 25, text: "Identifying what is known and still unclear" }, + { min: 45, text: "Selecting the next useful question" }, +]; + +const UPDATE_MESSAGES = [ + { min: 0, text: "Considering your answer" }, + { min: 10, text: "Updating the situation" }, + { min: 25, text: "Checking what changed" }, + { min: 45, text: "Choosing the next question" }, +]; + +function useLoadingStatus(messages, isLoading) { + const [elapsed, setElapsed] = useState(0); + const startRef = useRef(null); + + useEffect(() => { + if (isLoading) { + startRef.current = Date.now(); + const iv = setInterval(() => { + setElapsed(Math.floor((Date.now() - startRef.current) / 1000)); + }, 1000); + return () => clearInterval(iv); + } else { + setElapsed(0); + startRef.current = null; + } + }, [isLoading]); + + const currentMessage = useMemo(() => { + if (!messages || messages.length === 0) return ""; + let msg = messages[0].text; + for (const m of messages) { + if (elapsed >= m.min) msg = m.text; + } + return msg; + }, [messages, elapsed]); + + return { elapsed, currentMessage }; +} + export function UpdateErrorPanel({ updateError }) { if (!updateError) return null; @@ -134,6 +178,8 @@ export function UpdateErrorPanel({ updateError }) { ); } +export { INITIAL_MESSAGES, UPDATE_MESSAGES, useLoadingStatus }; + export default function ScenarioForm() { const [scenario, setScenario] = useState(""); const [status, setStatus] = useState("idle"); // idle | loading | error | success @@ -144,6 +190,16 @@ export default function ScenarioForm() { const [updateResult, setUpdateResult] = useState(null); const textareaRef = useRef(null); + const { elapsed: startElapsed, currentMessage: startMsg } = useLoadingStatus( + INITIAL_MESSAGES, + status === "loading" + ); + + const { elapsed: updateElapsed, currentMessage: updateMsg } = useLoadingStatus( + UPDATE_MESSAGES, + updateStatus === "loading" + ); + const handleSubmit = async (e) => { e.preventDefault(); setStatus("loading"); @@ -236,16 +292,28 @@ export default function ScenarioForm() { {scenario.length}/{MAX_LENGTH} - + {status === "idle" && ( + + )}