Commit ff6c3d7f authored by Kantz's avatar Kantz
Browse files

implementierung des Modus wechsels

parent 92ce484a
......@@ -3,20 +3,15 @@ from __future__ import annotations
import logging
from typing import List, Optional
import app.config as config
from app.deterministic_services import session_store
from app.deterministic_services.orchestrators.registry import (
get_default_orchestrator,
is_valid_orchestrator,
resolve_orchestrator,
)
from fastapi import APIRouter, HTTPException, Path, Query
from pydantic import BaseModel, Field
if config.get_orchestrator() == "tutor":
from app.deterministic_services.orchestrators import (
orchestrator_tutor as orchestrator,
)
elif config.get_orchestrator() == "task":
from app.deterministic_services.orchestrators import orchestrator_task as orchestrator
else:
from app.deterministic_services.orchestrators import orchestrator_qa as orchestrator
router = APIRouter()
logger = logging.getLogger(__name__)
......@@ -31,6 +26,7 @@ class ChatRequest(BaseModel):
messages: List[ChatMessage]
draft: Optional[str] = None
selected_task: Optional[dict[str, str]] = None
orchestrator: Optional[str] = None
class ChatResponse(BaseModel):
......@@ -48,6 +44,7 @@ class ChatArchiveSummary(BaseModel):
saved_at: str
message_count: int
preview: str
orchestrator: Optional[str] = None
class SelectedTaskRef(BaseModel):
......@@ -60,6 +57,7 @@ class ChatArchiveDetail(BaseModel):
saved_at: str
history: List[ChatMessage]
selected_task: Optional[SelectedTaskRef] = None
orchestrator: str
@router.post("/api/chat", response_model=ChatResponse)
......@@ -67,16 +65,24 @@ def chat(request: ChatRequest) -> ChatResponse:
if not request.messages:
raise HTTPException(status_code=400, detail="messages required")
requested_orchestrator = str(request.orchestrator or "").strip().lower()
if requested_orchestrator and not is_valid_orchestrator(requested_orchestrator):
raise HTTPException(status_code=422, detail="unsupported orchestrator")
orchestrator_name, orchestrator_impl = resolve_orchestrator(
requested_orchestrator or None
)
try:
payload_messages = [{"role": m.role, "content": m.text} for m in request.messages]
if config.get_orchestrator() == "task":
result = orchestrator.run_chat(
if orchestrator_name == "task":
result = orchestrator_impl.run_chat(
payload_messages,
draft=request.draft,
selected_task=request.selected_task,
)
else:
result = orchestrator.run_chat(
result = orchestrator_impl.run_chat(
payload_messages,
draft=request.draft,
)
......@@ -124,6 +130,7 @@ def get_archive(chat_id: str = Path(..., min_length=1)) -> ChatArchiveDetail:
saved_at=record.get("saved_at", ""),
history=[ChatMessage(role=item["role"], text=item["text"]) for item in record["history"]],
selected_task=selected_task,
orchestrator=record.get("orchestrator") or get_default_orchestrator(),
)
......@@ -132,10 +139,16 @@ def archive_chat(request: ChatRequest) -> ChatArchiveResponse:
if not request.messages:
return ChatArchiveResponse(status="skipped", chat_id="unknown")
requested_orchestrator = str(request.orchestrator or "").strip().lower()
if requested_orchestrator and not is_valid_orchestrator(requested_orchestrator):
raise HTTPException(status_code=422, detail="unsupported orchestrator")
archive_orchestrator = requested_orchestrator or get_default_orchestrator()
try:
chat_id = session_store.archive_chat(
[{"role": m.role, "content": m.text} for m in request.messages],
draft=request.draft,
orchestrator=archive_orchestrator,
)
except Exception as exc:
logger.exception("Chat archive failed")
......
from __future__ import annotations
from fastapi import APIRouter
from pydantic import BaseModel
from app.deterministic_services.orchestrators.registry import (
AVAILABLE_ORCHESTRATORS,
get_default_orchestrator,
)
router = APIRouter()
class OrchestratorConfigResponse(BaseModel):
default_orchestrator: str
available_orchestrators: list[str]
@router.get("/api/orchestrator/config", response_model=OrchestratorConfigResponse)
def get_orchestrator_config() -> OrchestratorConfigResponse:
return OrchestratorConfigResponse(
default_orchestrator=get_default_orchestrator(),
available_orchestrators=list(AVAILABLE_ORCHESTRATORS),
)
from __future__ import annotations
from typing import Any
import app.config as config
from app.deterministic_services.orchestrators import (
orchestrator_qa,
orchestrator_task,
orchestrator_tutor,
)
AVAILABLE_ORCHESTRATORS: tuple[str, ...] = ("qa", "tutor", "task")
_ORCHESTRATOR_MODULES: dict[str, Any] = {
"qa": orchestrator_qa,
"tutor": orchestrator_tutor,
"task": orchestrator_task,
}
def get_default_orchestrator() -> str:
configured = str(config.get_orchestrator() or "").strip().lower()
if configured in _ORCHESTRATOR_MODULES:
return configured
return "qa"
def is_valid_orchestrator(value: str) -> bool:
return value in _ORCHESTRATOR_MODULES
def resolve_orchestrator(value: str | None = None) -> tuple[str, Any]:
if value:
normalized = value.strip().lower()
if normalized in _ORCHESTRATOR_MODULES:
return normalized, _ORCHESTRATOR_MODULES[normalized]
default_name = get_default_orchestrator()
return default_name, _ORCHESTRATOR_MODULES[default_name]
......@@ -26,7 +26,11 @@ def _extract_selected_task(sheet: dict[str, Any]) -> dict[str, str] | None:
return {"file_id": file_id, "task_id": task_id}
def archive_chat(messages: list[dict[str, Any]], draft: str | None = None) -> str:
def archive_chat(
messages: list[dict[str, Any]],
draft: str | None = None,
orchestrator: str | None = None,
) -> str:
chat_id = context_store.get_chat_id(messages, draft=draft)
sheet = context_store.load_sheet(chat_id)
if not sheet:
......@@ -38,6 +42,7 @@ def archive_chat(messages: list[dict[str, Any]], draft: str | None = None) -> st
record = {
"chat_id": chat_id,
"saved_at": _utc_now(),
"orchestrator": str(orchestrator or "").strip().lower() or None,
"history": sheet.get("history", []),
"context_sheet": context_store.format_sheet(sheet),
"retrieval_contexts": sheet.get("retrieval_contexts", []),
......@@ -65,6 +70,7 @@ def _summarize_record(record: dict[str, Any]) -> dict[str, Any]:
return {
"chat_id": record.get("chat_id", "unknown"),
"saved_at": record.get("saved_at", ""),
"orchestrator": record.get("orchestrator"),
"message_count": len(history),
"preview": preview,
}
......@@ -123,6 +129,7 @@ def load_archive(chat_id: str) -> dict[str, Any] | None:
return {
"chat_id": record.get("chat_id", chat_id),
"saved_at": record.get("saved_at", ""),
"orchestrator": record.get("orchestrator"),
"history": history,
"selected_task": selected_task,
}
......
......@@ -3,7 +3,7 @@ import logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api import canvas, chat, health, context, tasks
from app.api import canvas, chat, context, health, orchestrator, tasks
from app.config import get_frontend_url
from app.deterministic_services import embedding_provider
......@@ -36,4 +36,5 @@ app.include_router(chat.router)
app.include_router(canvas.router)
app.include_router(context.router)
app.include_router(health.router)
app.include_router(orchestrator.router)
app.include_router(tasks.router)
export type OrchestratorName = "qa" | "tutor" | "task";
export type OrchestratorConfigResponse = {
default_orchestrator: OrchestratorName;
available_orchestrators: OrchestratorName[];
};
const FALLBACK_ORCHESTRATORS: OrchestratorName[] = ["qa", "tutor", "task"];
const normalizeOrchestrator = (value: string): OrchestratorName | null => {
if (value === "qa" || value === "tutor" || value === "task") {
return value;
}
return null;
};
export const getFallbackOrchestrators = (): OrchestratorName[] =>
[...FALLBACK_ORCHESTRATORS];
export async function fetchOrchestratorConfig(): Promise<OrchestratorConfigResponse> {
const response = await fetch("/api/orchestrator/config");
if (!response.ok) {
throw new Error(`Orchestrator config failed: ${response.status}`);
}
const payload = await response.json();
const defaultOrchestrator =
normalizeOrchestrator(String(payload?.default_orchestrator || "")) || "qa";
const availableRaw = Array.isArray(payload?.available_orchestrators)
? payload.available_orchestrators
: [];
const available = availableRaw
.map((item: unknown) => normalizeOrchestrator(String(item || "")))
.filter((item: OrchestratorName | null): item is OrchestratorName => Boolean(item));
return {
default_orchestrator: defaultOrchestrator,
available_orchestrators: available.length ? available : getFallbackOrchestrators(),
};
}
import type { OrchestratorName } from "../../api/orchestratorApi";
import { t } from "../../i18n";
type OrchestratorSelectProps = {
value: OrchestratorName;
options: OrchestratorName[];
onChange: (value: OrchestratorName) => void;
disabled?: boolean;
};
const labelMap: Record<OrchestratorName, string> = {
qa: "QA",
tutor: "Tutor",
task: "Task",
};
export default function OrchestratorSelect({
value,
options,
onChange,
disabled = false,
}: OrchestratorSelectProps) {
return (
<label className="orchestrator-select-wrap" htmlFor="orchestrator-select">
<span className="orchestrator-select-label">{t("orchestratorMode")}</span>
<select
id="orchestrator-select"
className="task-select orchestrator-select"
value={value}
onChange={(event) => onChange(event.target.value as OrchestratorName)}
disabled={disabled}
>
{options.map((option) => (
<option key={option} value={option}>
{labelMap[option]}
</option>
))}
</select>
</label>
);
}
......@@ -16,6 +16,7 @@
show: "Show",
newChat: "New Chat",
loading: "Loading...",
orchestratorMode: "Mode",
directChildren: "Direct children",
taskChildren: "Task sources",
indirectChildren: "Indirect children",
......@@ -35,6 +36,7 @@
noSavedChatsYet: "No saved chats yet.",
canvasHidden: "Canvas hidden",
failedLoadTasks: "Could not load tasks.",
failedLoadOrchestratorConfig: "Could not load orchestrator config. Fallback mode active.",
chatRequestFailed: "The chat request failed. Please check backend logs.",
retrievalFailed: "Source retrieval failed. Please check backend logs.",
failedLoadSavedChats: "Could not load saved chats.",
......@@ -72,6 +74,7 @@
show: "anzeigen",
newChat: "neuer Chat",
loading: "Lade...",
orchestratorMode: "Modus",
directChildren: "Direkte Quellen",
taskChildren: "Aufgaben-Quellen",
indirectChildren: "Indirekte Quellen",
......@@ -91,6 +94,8 @@
noSavedChatsYet: "Noch keine gespeicherten Chats.",
canvasHidden: "Canvas ausgeblendet",
failedLoadTasks: "Aufgaben konnten nicht geladen werden.",
failedLoadOrchestratorConfig:
"Orchestrator-Konfiguration konnte nicht geladen werden. Fallback-Modus aktiv.",
chatRequestFailed:
"Chat-Anfrage ist fehlgeschlagen. Bitte pruefe die Backend-Logs.",
retrievalFailed:
......
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import "../styles/theme.css";
import ChatWindow from "../components/Chat/ChatWindow";
import CanvasDrawer from "../components/Canvas/CanvasDrawer";
import OrchestratorSelect from "../components/Orchestrator/OrchestratorSelect";
import DocPanel from "../components/Retrieval/DocPanel";
import TaskPanel from "../components/Task/TaskPanel";
import type { ChatMessage } from "../components/Chat/MessageList";
import type { RetrievedDoc } from "../components/Retrieval/DocPanel";
import { t } from "../i18n";
import type { OrchestratorName } from "../api/orchestratorApi";
import { selectTask } from "../api/taskApi";
import { t } from "../i18n";
import { createSessionId, useTutorSession } from "../state/tutorSession";
const initialMessages: ChatMessage[] = [];
const normalizeOrchestrator = (value: string | null | undefined): OrchestratorName | null => {
if (value === "qa" || value === "tutor" || value === "task") {
return value;
}
return null;
};
type ArchivedChatSummary = {
chat_id: string;
saved_at: string;
message_count: number;
preview: string;
orchestrator?: string | null;
};
type ArchivedChatDetail = {
chat_id: string;
saved_at: string;
history: ChatMessage[];
orchestrator?: string | null;
selected_task?: {
file_id: string;
task_id: string;
......@@ -91,9 +102,7 @@ const toRetrievedDoc = (source: ContextSource, index: number): RetrievedDoc => (
});
export default function ChatPage() {
const showChatsButton = String(
import.meta.env.VITE_SHOW_CHATS_BUTTON ?? "true"
)
const showChatsButton = String(import.meta.env.VITE_SHOW_CHATS_BUTTON ?? "true")
.trim()
.toLowerCase() !== "false";
const navigate = useNavigate();
......@@ -105,6 +114,12 @@ export default function ChatPage() {
selectedTaskRef,
selectedTask,
selectedTaskFile,
selectedOrchestrator,
availableOrchestrators,
setSelectedOrchestrator,
switchOrchestrator,
isOrchestratorSelectable,
orchestratorError,
taskLocked,
lockTask,
unlockTask,
......@@ -139,13 +154,7 @@ export default function ChatPage() {
if (!selectedTaskRef || !taskLocked) {
navigate("/select-task", { replace: true });
}
}, [
isTaskModeEnabled,
isTasksInitialized,
navigate,
selectedTaskRef,
taskLocked,
]);
}, [isTaskModeEnabled, isTasksInitialized, navigate, selectedTaskRef, taskLocked]);
const docIndexes = useMemo(() => {
const bySourceKey: Record<string, RetrievedDoc> = {};
......@@ -243,6 +252,7 @@ export default function ChatPage() {
const chatPayload: {
messages: Array<{ role: "user" | "assistant"; text: string }>;
draft: string;
orchestrator: OrchestratorName;
selected_task?: { file_id: string; task_id: string };
} = {
messages: [...messages, userMessage].map((message) => ({
......@@ -250,6 +260,7 @@ export default function ChatPage() {
text: message.text,
})),
draft: chatSessionId,
orchestrator: selectedOrchestrator,
};
if (isTaskModeEnabled && selectedTaskRef) {
chatPayload.selected_task = {
......@@ -410,7 +421,14 @@ export default function ChatPage() {
setMessages(payload.history || []);
setChatSessionId(payload.chat_id);
if (isTaskModeEnabled) {
const restoredOrchestrator = normalizeOrchestrator(payload.orchestrator);
const nextOrchestrator =
restoredOrchestrator && availableOrchestrators.includes(restoredOrchestrator)
? restoredOrchestrator
: selectedOrchestrator;
setSelectedOrchestrator(nextOrchestrator);
if (nextOrchestrator === "task") {
const restoredTask = payload.selected_task;
if (restoredTask?.file_id && restoredTask?.task_id) {
setTaskRef({
......@@ -432,6 +450,10 @@ export default function ChatPage() {
unlockTask();
navigate("/select-task", { replace: true });
}
} else {
setTaskRef(null);
unlockTask();
navigate("/chat", { replace: true });
}
} catch (error) {
setArchiveError(t("failedLoadSelectedChat"));
......@@ -452,6 +474,7 @@ export default function ChatPage() {
text: message.text,
})),
draft: chatSessionId,
orchestrator: selectedOrchestrator,
}),
});
} catch (error) {
......@@ -484,6 +507,7 @@ export default function ChatPage() {
text: message.text,
})),
draft: chatSessionId,
orchestrator: selectedOrchestrator,
}),
});
} catch (error) {
......@@ -533,6 +557,15 @@ export default function ChatPage() {
void loadArchives();
};
const handleSwitchOrchestrator = (next: OrchestratorName) => {
if (next === selectedOrchestrator) {
return;
}
resetChatState();
switchOrchestrator(next);
navigate(next === "task" ? "/select-task" : "/chat");
};
const handleCanvasSave = async (dataUrl: string) => {
setCanvasStatus({
kind: "info",
......@@ -655,11 +688,7 @@ export default function ChatPage() {
<div className="app-shell">
<header className="app-header">
{showChatsButton ? (
<button
type="button"
className="btn sidebar-toggle"
onClick={handleOpenSidebar}
>
<button type="button" className="btn sidebar-toggle" onClick={handleOpenSidebar}>
{t("chats")}
</button>
) : null}
......@@ -671,11 +700,18 @@ export default function ChatPage() {
</div>
</div>
<div className="header-meta">
<OrchestratorSelect
value={selectedOrchestrator}
options={availableOrchestrators}
onChange={handleSwitchOrchestrator}
disabled={!isOrchestratorSelectable}
/>
<span className="pill">Postgres + pgvector</span>
<span className="pill">Python FastAPI</span>
<span className="pill">Mathpix</span>
</div>
</header>
{orchestratorError ? <div className="chat-archive-error">{orchestratorError}</div> : null}
<main className="app-main">
<section className="chat-column">
......@@ -701,11 +737,7 @@ export default function ChatPage() {
) : null}
</section>
<aside
className={`retrieval-column ${
isTaskModeEnabled ? "retrieval-column-task-mode" : ""
}`}
>
<aside className={`retrieval-column ${isTaskModeEnabled ? "retrieval-column-task-mode" : ""}`}>
{isTaskModeEnabled && selectedTask ? (
<TaskPanel
readOnly
......@@ -736,23 +768,14 @@ export default function ChatPage() {
<div className={`sidebar ${isSidebarOpen ? "active" : ""}`}>
<div className="sd-header">
<h4 className="sd-title">{t("savedChats")}</h4>
<button
type="button"
className="sidebar-button"
onClick={() => setIsSidebarOpen(false)}
>
<button type="button" className="sidebar-button" onClick={() => setIsSidebarOpen(false)}>
X
</button>
</div>
<div className="sd-body">
<ul className="sd-list">
<li>
<button
type="button"
className="sd-link"
onClick={handleNewChat}
disabled={isArchiving}
>
<button type="button" className="sd-link" onClick={handleNewChat} disabled={isArchiving}>
{isArchiving ? t("saving") : t("newChat")}
</button>
</li>
......@@ -764,9 +787,7 @@ export default function ChatPage() {
<li key={item.chat_id}>
<button
type="button"
className={`sd-link ${
selectedArchiveId === item.chat_id ? "active" : ""
}`}
className={`sd-link ${selectedArchiveId === item.chat_id ? "active" : ""}`}
onClick={() => {
setSelectedArchiveId(item.chat_id);
void handleLoadArchive(item.chat_id);
......@@ -782,9 +803,7 @@ export default function ChatPage() {
</li>
)}
</ul>
{archiveError ? (
<div className="chat-archive-error">{archiveError}</div>
) : null}
{archiveError ? <div className="chat-archive-error">{archiveError}</div> : null}
</div>
</div>
<div
......
import { useEffect, useRef } from "react";
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import "../styles/theme.css";
import { t } from "../i18n";
import type { OrchestratorName } from "../api/orchestratorApi";
import OrchestratorSelect from "../components/Orchestrator/OrchestratorSelect";
import { selectTask } from "../api/taskApi";
import { t } from "../i18n";
import { useTutorSession } from "../state/tutorSession";
import "../styles/theme.css";
export default function TaskSelectionPage() {
const navigate = useNavigate();
......@@ -11,6 +13,11 @@ export default function TaskSelectionPage() {
chatSessionId,
isTaskModeEnabled,
isTasksInitialized,
selectedOrchestrator,
availableOrchestrators,
switchOrchestrator,
isOrchestratorSelectable,
orchestratorError,
selectedTaskRef,
selectedTask,
taskFileOptions,
......@@ -63,6 +70,14 @@ export default function TaskSelectionPage() {
}
};
const handleSwitchOrchestrator = (next: OrchestratorName) => {
if (next === selectedOrchestrator) {
return;
}
switchOrchestrator(next);
navigate(next === "task" ? "/select-task" : "/chat", { replace: true });
};
if (!isTasksInitialized) {
return <div className="app-loading">{t("loading")}</div>;
}
......@@ -77,7 +92,16 @@ export default function TaskSelectionPage() {
<div className="brand-subtitle">{t("taskSelectionSubtitle")}</div>
</div>
</div>
<div className="header-meta">
<OrchestratorSelect
value={selectedOrchestrator}
options={availableOrchestrators}
onChange={handleSwitchOrchestrator}
disabled={!isOrchestratorSelectable}
/>
</div>
</header>
{orchestratorError ? <div className="chat-archive-error">{orchestratorError}</div> : null}
<main className="task-select-main">
<section className="task-select-card">
......
......@@ -9,6 +9,11 @@ import {
type PropsWithChildren,
} from "react";
import { t } from "../i18n";
import {
fetchOrchestratorConfig,
getFallbackOrchestrators,
type OrchestratorName,
} from "../api/orchestratorApi";
import { fetchTasks, type SelectedTaskRef, type TaskFile } from "../api/taskApi";
export type SelectOption = {
......@@ -38,6 +43,12 @@ export type TutorSessionState = TaskSelectionState & {
chatSessionId: string;
setChatSessionId: (value: string) => void;
initTasks: () => Promise<void>;
selectedOrchestrator: OrchestratorName;
availableOrchestrators: OrchestratorName[];
setSelectedOrchestrator: (value: OrchestratorName) => void;
switchOrchestrator: (value: OrchestratorName) => void;
isOrchestratorSelectable: boolean;
orchestratorError: string | null;
setTaskRef: (value: SelectedTaskRef | null) => void;
setTaskFile: (fileId: string) => void;
setTaskId: (taskId: string) => void;
......@@ -56,13 +67,21 @@ export const getDefaultTaskId = (tasks: Array<{ task_id: string }>): string =>
export function TutorSessionProvider({ children }: PropsWithChildren) {
const [chatSessionId, setChatSessionId] = useState<string>(() => createSessionId());
const [isTaskModeEnabled, setIsTaskModeEnabled] = useState(false);
const [selectedOrchestrator, setSelectedOrchestratorState] =
useState<OrchestratorName>("qa");
const [availableOrchestrators, setAvailableOrchestrators] = useState<OrchestratorName[]>(
() => getFallbackOrchestrators()
);
const [isOrchestratorSelectable, setIsOrchestratorSelectable] = useState(false);
const [orchestratorError, setOrchestratorError] = useState<string | null>(null);
const [isTasksInitialized, setIsTasksInitialized] = useState(false);
const [taskFiles, setTaskFiles] = useState<TaskFile[]>([]);
const [selectedTaskRef, setSelectedTaskRef] = useState<SelectedTaskRef | null>(null);
const [tasksError, setTasksError] = useState<string | null>(null);
const [taskLocked, setTaskLocked] = useState(false);
const isTaskModeEnabled = selectedOrchestrator === "task";
const selectedTask = useMemo<SelectedTask | null>(() => {
if (!selectedTaskRef) {
return null;
......@@ -108,17 +127,30 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
const initTasks = useCallback(async () => {
setTasksError(null);
setOrchestratorError(null);
try {
const orchestratorPayload = await fetchOrchestratorConfig();
const available = orchestratorPayload.available_orchestrators.length
? orchestratorPayload.available_orchestrators
: getFallbackOrchestrators();
setAvailableOrchestrators(available);
setIsOrchestratorSelectable(true);
setSelectedOrchestratorState((prev) =>
available.includes(prev) ? prev : orchestratorPayload.default_orchestrator
);
} catch (error) {
setAvailableOrchestrators(getFallbackOrchestrators());
setSelectedOrchestratorState("qa");
setIsOrchestratorSelectable(false);
setOrchestratorError(t("failedLoadOrchestratorConfig"));
void error;
}
try {
const payload = await fetchTasks();
const enabled = Boolean(payload.enabled);
const files = payload.task_files || [];
setIsTaskModeEnabled(enabled);
setTaskFiles(files);
if (!enabled) {
setSelectedTaskRef(null);
} else {
setSelectedTaskRef((prev) => {
if (prev) {
const file = files.find((item) => item.file_id === prev.fileId);
......@@ -137,10 +169,8 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
}
return { fileId: firstFile.file_id, taskId: defaultTaskId };
});
}
} catch (error) {
setTasksError(t("failedLoadTasks"));
setIsTaskModeEnabled(false);
setTaskFiles([]);
setSelectedTaskRef(null);
void error;
......@@ -153,7 +183,8 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
void initTasks();
}, [initTasks]);
const setTaskFile = useCallback((fileId: string) => {
const setTaskFile = useCallback(
(fileId: string) => {
if (!fileId) {
return;
}
......@@ -166,14 +197,32 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
return;
}
setSelectedTaskRef({ fileId: file.file_id, taskId: defaultTaskId });
}, [taskFiles]);
},
[taskFiles]
);
const setTaskId = useCallback((taskId: string) => {
const setTaskId = useCallback(
(taskId: string) => {
if (!taskId || !selectedTaskRef?.fileId) {
return;
}
setSelectedTaskRef({ fileId: selectedTaskRef.fileId, taskId });
}, [selectedTaskRef]);
},
[selectedTaskRef]
);
const setSelectedOrchestrator = useCallback((value: OrchestratorName) => {
setSelectedOrchestratorState(value);
}, []);
const switchOrchestrator = useCallback((value: OrchestratorName) => {
setSelectedOrchestratorState(value);
setChatSessionId(createSessionId());
setTaskLocked(false);
if (value !== "task") {
setSelectedTaskRef(null);
}
}, []);
const lockTask = useCallback(() => {
setTaskLocked(true);
......@@ -193,6 +242,12 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
chatSessionId,
setChatSessionId,
initTasks,
selectedOrchestrator,
availableOrchestrators,
setSelectedOrchestrator,
switchOrchestrator,
isOrchestratorSelectable,
orchestratorError,
taskFiles,
selectedTaskRef,
selectedTask,
......
......@@ -61,6 +61,27 @@ body {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.orchestrator-select-wrap {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border: 1px solid #d8d1c4;
border-radius: 999px;
background: #fef9f0;
}
.orchestrator-select-label {
font-size: 12px;
color: #6f675d;
}
.orchestrator-select {
min-width: 96px;
padding: 4px 8px;
}
.pill {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment