Commit 9c79c5ca authored by Kantz's avatar Kantz
Browse files

task panel exptrahiert

parent 0893fd21
......@@ -177,6 +177,13 @@ def select_task_for_context(
def build_task_catalog(task_files: list[dict[str, Any]] | None = None) -> list[dict[str, Any]]:
catalog = task_files if task_files is not None else load_task_files()
catalog = sorted(
catalog,
key=lambda item: (
str(item.get("title", "")).strip().lower(),
str(item.get("_file_id", "")).strip().lower(),
),
)
response: list[dict[str, Any]] = []
for task_file in catalog:
file_id = str(task_file.get("_file_id", ""))
......@@ -196,6 +203,7 @@ def build_task_catalog(task_files: list[dict[str, Any]] | None = None) -> list[d
"full_text": "\n".join(full_text_parts),
}
)
tasks.sort(key=lambda item: item["task_id"])
response.append(
{
"file_id": file_id,
......
import { useEffect, useRef } from "react";
import { t } from "../../i18n";
type SelectOption = {
value: string;
label: string;
};
type TaskPanelProps = {
selectedFileId: string;
fileOptions: SelectOption[];
selectedTaskId: string;
taskOptions: SelectOption[];
selectedTaskText: string;
tasksError?: string | null;
onTaskFileChange: (fileId: string) => void;
onTaskChange: (taskId: string) => void;
};
export default function TaskPanel({
selectedFileId,
fileOptions,
selectedTaskId,
taskOptions,
selectedTaskText,
tasksError,
onTaskFileChange,
onTaskChange,
}: TaskPanelProps) {
const taskDisplayRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!selectedTaskText || !taskDisplayRef.current) {
return;
}
const mathjax = window.MathJax;
if (!mathjax?.typesetPromise) {
return;
}
mathjax.typesetPromise([taskDisplayRef.current]).catch(() => undefined);
}, [selectedTaskText]);
return (
<section className="task-panel">
<div className="task-panel-header">
<div className="task-panel-title">{t("task")}</div>
<div className="task-panel-controls">
<select
className="task-select"
value={selectedFileId}
onChange={(event) => onTaskFileChange(event.target.value)}
disabled={!fileOptions.length}
>
{fileOptions.length ? (
fileOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTasksAvailable")}</option>
)}
</select>
<select
className="task-select"
value={selectedTaskId}
onChange={(event) => onTaskChange(event.target.value)}
disabled={!taskOptions.length}
>
{taskOptions.length ? (
taskOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTasksAvailable")}</option>
)}
</select>
</div>
</div>
<div className="task-panel-content" ref={taskDisplayRef}>
{selectedTaskText || t("noTaskSelected")}
</div>
{tasksError ? <div className="task-panel-error">{tasksError}</div> : null}
</section>
);
}
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import "../styles/theme.css";
import ChatWindow from "../components/Chat/ChatWindow";
import CanvasDrawer from "../components/Canvas/CanvasDrawer";
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";
......@@ -83,6 +84,9 @@ type SelectedTask = SelectedTaskRef & {
fullText: string;
};
const getDefaultTaskId = (tasks: TaskItem[]): string =>
tasks.find((task) => task.task_id === "01")?.task_id || tasks[0]?.task_id || "";
const sourceIdToKey = (sourceId: ContextSource["source_id"]) => {
const parts = [
sourceId.chapter_title ?? "",
......@@ -144,7 +148,6 @@ export default function App() {
null
);
const [tasksError, setTasksError] = useState<string | null>(null);
const taskDisplayRef = useRef<HTMLDivElement | null>(null);
const docIndexes = useMemo(() => {
const bySourceKey: Record<string, RetrievedDoc> = {};
......@@ -209,17 +212,30 @@ export default function App() {
};
}, [selectedTaskRef, taskFiles]);
const taskOptions = useMemo(
const selectedTaskFile = useMemo(
() =>
taskFiles.find((file) => file.file_id === selectedTaskRef?.fileId) || null,
[selectedTaskRef?.fileId, taskFiles]
);
const taskFileOptions = useMemo(
() =>
taskFiles.flatMap((file) =>
file.tasks.map((task) => ({
value: `${file.file_id}::${task.task_id}`,
label: `${file.title} - ${task.task_id}`,
}))
),
taskFiles.map((file) => ({
value: file.file_id,
label: file.title || file.file_id,
})),
[taskFiles]
);
const taskOptions = useMemo(
() =>
(selectedTaskFile?.tasks || []).map((task) => ({
value: task.task_id,
label: task.task_id,
})),
[selectedTaskFile]
);
useEffect(() => {
let isActive = true;
......@@ -237,16 +253,7 @@ export default function App() {
const enabled = Boolean(payload.enabled);
setIsTaskModeEnabled(enabled);
setTaskFiles(payload.task_files || []);
if (enabled && payload.task_files?.length) {
const firstFile = payload.task_files[0];
const firstTask = firstFile.tasks?.[0];
if (firstTask) {
setSelectedTaskRef((prev) =>
prev || { fileId: firstFile.file_id, taskId: firstTask.task_id }
);
}
} else {
if (!enabled) {
setSelectedTaskRef(null);
}
} catch (error) {
......@@ -267,6 +274,39 @@ export default function App() {
};
}, []);
useEffect(() => {
if (!isTaskModeEnabled) {
return;
}
if (!taskFiles.length) {
setSelectedTaskRef(null);
return;
}
setSelectedTaskRef((prev) => {
if (prev) {
const file = taskFiles.find((item) => item.file_id === prev.fileId);
if (file) {
const hasTask = file.tasks.some((task) => task.task_id === prev.taskId);
if (hasTask) {
return prev;
}
const defaultTaskId = getDefaultTaskId(file.tasks);
if (defaultTaskId) {
return { fileId: file.file_id, taskId: defaultTaskId };
}
}
}
const firstFile = taskFiles[0];
const defaultTaskId = getDefaultTaskId(firstFile.tasks || []);
if (!defaultTaskId) {
return null;
}
return { fileId: firstFile.file_id, taskId: defaultTaskId };
});
}, [isTaskModeEnabled, taskFiles]);
useEffect(() => {
if (!isTaskModeEnabled || !selectedTaskRef) {
return;
......@@ -291,17 +331,6 @@ export default function App() {
void syncTaskSelection();
}, [chatSessionId, isTaskModeEnabled, selectedTaskRef]);
useEffect(() => {
if (!isTaskModeEnabled || !selectedTask?.fullText || !taskDisplayRef.current) {
return;
}
const mathjax = window.MathJax;
if (!mathjax?.typesetPromise) {
return;
}
mathjax.typesetPromise([taskDisplayRef.current]).catch(() => undefined);
}, [isTaskModeEnabled, selectedTask?.fullText]);
const handleSend = async () => {
const trimmed = draft.trim();
if (!trimmed) {
......@@ -438,12 +467,26 @@ export default function App() {
setIsCanvasVisible((prev) => !prev);
};
const handleTaskChange = (value: string) => {
const [fileId, taskId] = value.split("::");
if (!fileId || !taskId) {
const handleTaskFileChange = (fileId: string) => {
if (!fileId) {
return;
}
setSelectedTaskRef({ fileId, taskId });
const file = taskFiles.find((item) => item.file_id === fileId);
if (!file) {
return;
}
const defaultTaskId = getDefaultTaskId(file.tasks || []);
if (!defaultTaskId) {
return;
}
setSelectedTaskRef({ fileId: file.file_id, taskId: defaultTaskId });
};
const handleTaskChange = (taskId: string) => {
if (!taskId || !selectedTaskRef?.fileId) {
return;
}
setSelectedTaskRef({ fileId: selectedTaskRef.fileId, taskId });
};
const resetChatState = () => {
......@@ -675,35 +718,16 @@ export default function App() {
<main className="app-main">
<section className="chat-column">
{isTaskModeEnabled ? (
<section className="task-panel">
<div className="task-panel-header">
<div className="task-panel-title">{t("task")}</div>
<select
className="task-select"
value={
selectedTaskRef
? `${selectedTaskRef.fileId}::${selectedTaskRef.taskId}`
: ""
}
onChange={(event) => handleTaskChange(event.target.value)}
disabled={!taskOptions.length}
>
{taskOptions.length ? (
taskOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<option value="">{t("noTasksAvailable")}</option>
)}
</select>
</div>
<div className="task-panel-content" ref={taskDisplayRef}>
{selectedTask?.fullText || t("noTaskSelected")}
</div>
{tasksError ? <div className="task-panel-error">{tasksError}</div> : null}
</section>
<TaskPanel
selectedFileId={selectedTaskRef?.fileId || ""}
fileOptions={taskFileOptions}
selectedTaskId={selectedTaskRef?.taskId || ""}
taskOptions={taskOptions}
selectedTaskText={selectedTask?.fullText || ""}
tasksError={tasksError}
onTaskFileChange={handleTaskFileChange}
onTaskChange={handleTaskChange}
/>
) : null}
<ChatWindow
......
......@@ -112,7 +112,6 @@ body {
.task-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
......@@ -121,6 +120,14 @@ body {
font-weight: 600;
}
.task-panel-controls {
margin-left: auto;
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.task-select {
border: 1px solid #d8d1c4;
border-radius: 10px;
......@@ -551,6 +558,12 @@ body {
width: 100%;
}
.task-panel-controls {
width: 100%;
margin-left: 0;
justify-content: flex-start;
}
.composer-row .btn {
flex: 1 1 120px;
}
......
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