|
|
@@ -1,14 +1,32 @@
|
|
|
-import { useCallback, useEffect, useState } from "react";
|
|
|
+import { useCallback, useEffect, useState, useRef } from "react";
|
|
|
import type { FC } from "react";
|
|
|
+import { Modal, Form, Toast } from "@douyinfe/semi-ui";
|
|
|
import { traceApi } from "../../api/traceApi";
|
|
|
+import type { Goal } from "../../types/goal";
|
|
|
+import type { Message } from "../../types/message";
|
|
|
import styles from "./TopBar.module.css";
|
|
|
|
|
|
interface TopBarProps {
|
|
|
+ selectedTraceId: string | null;
|
|
|
+ selectedNode: Goal | Message | null;
|
|
|
onTraceSelect: (traceId: string) => void;
|
|
|
+ onTraceCreated?: () => void;
|
|
|
}
|
|
|
|
|
|
-export const TopBar: FC<TopBarProps> = ({ onTraceSelect }) => {
|
|
|
+export const TopBar: FC<TopBarProps> = ({ selectedTraceId, selectedNode, onTraceSelect, onTraceCreated }) => {
|
|
|
const [title, setTitle] = useState("流程图可视化系统");
|
|
|
+ const [isModalVisible, setIsModalVisible] = useState(false);
|
|
|
+ const [isExperienceModalVisible, setIsExperienceModalVisible] = useState(false);
|
|
|
+ const [experienceContent, setExperienceContent] = useState("");
|
|
|
+ const formApiRef = useRef<{ getValues: () => { system_prompt: string; user_prompt: string } } | null>(null);
|
|
|
+
|
|
|
+ const isMessageNode = (node: Goal | Message): node is Message =>
|
|
|
+ "message_id" in node || "role" in node || "content" in node || "goal_id" in node || "tokens" in node;
|
|
|
+ const getMessageId = (node: Message) => {
|
|
|
+ if (typeof node.message_id === "string" && node.message_id) return node.message_id;
|
|
|
+ if (typeof node.id === "string" && node.id) return node.id;
|
|
|
+ return null;
|
|
|
+ };
|
|
|
|
|
|
const loadTraces = useCallback(
|
|
|
async (status?: string) => {
|
|
|
@@ -19,7 +37,6 @@ export const TopBar: FC<TopBarProps> = ({ onTraceSelect }) => {
|
|
|
});
|
|
|
const firstTrace = data.traces[0];
|
|
|
const traceId = firstTrace?.parent_trace_id || firstTrace.trace_id;
|
|
|
- console.log("%c [ firstTrace ]-24", "font-size:13px; background:pink; color:#bf2c9f;", firstTrace);
|
|
|
if (firstTrace) {
|
|
|
setTitle(firstTrace.task);
|
|
|
onTraceSelect(traceId);
|
|
|
@@ -38,6 +55,99 @@ export const TopBar: FC<TopBarProps> = ({ onTraceSelect }) => {
|
|
|
loadTraces();
|
|
|
}, [loadTraces]);
|
|
|
|
|
|
+ const handleNewTask = () => {
|
|
|
+ setIsModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleConfirm = async () => {
|
|
|
+ try {
|
|
|
+ const values = formApiRef.current?.getValues();
|
|
|
+ if (!values) return;
|
|
|
+
|
|
|
+ await traceApi.createTrace(values);
|
|
|
+ await loadTraces();
|
|
|
+ onTraceCreated?.();
|
|
|
+ setIsModalVisible(false);
|
|
|
+ Toast.success("创建成功");
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to create trace:", error);
|
|
|
+ Toast.error("创建失败");
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleRun = async () => {
|
|
|
+ console.log("%c [ selectedNode ]-72", "font-size:13px; background:pink; color:#bf2c9f;", selectedNode);
|
|
|
+
|
|
|
+ if (!selectedNode) {
|
|
|
+ Toast.warning("请选择插入节点");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isMessageNode(selectedNode)) {
|
|
|
+ Toast.warning("插入位置错误");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const messageId = getMessageId(selectedNode);
|
|
|
+ if (!messageId) {
|
|
|
+ Toast.error("消息ID缺失");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await traceApi.runTrace(messageId);
|
|
|
+ Toast.success("已开始运行");
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to run trace:", error);
|
|
|
+ Toast.error("运行请求失败");
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleStop = () => {
|
|
|
+ if (!selectedTraceId) {
|
|
|
+ Toast.warning("请先选择一个 Trace");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Modal.confirm({
|
|
|
+ title: "确认停止",
|
|
|
+ content: "确定要停止当前运行的 Trace 吗?",
|
|
|
+ onOk: async () => {
|
|
|
+ try {
|
|
|
+ await traceApi.stopTrace(selectedTraceId);
|
|
|
+ Toast.success("已停止");
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to stop trace:", error);
|
|
|
+ Toast.error("停止失败");
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleReflect = async () => {
|
|
|
+ if (!selectedTraceId) {
|
|
|
+ Toast.warning("请先选择一个 Trace");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await traceApi.reflectTrace(selectedTraceId);
|
|
|
+ Toast.success("已触发反思");
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to reflect trace:", error);
|
|
|
+ Toast.error("反思请求失败");
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleExperience = async () => {
|
|
|
+ try {
|
|
|
+ const content = await traceApi.getExperiences();
|
|
|
+ setExperienceContent(typeof content === "string" ? content : JSON.stringify(content, null, 2));
|
|
|
+ setIsExperienceModalVisible(true);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to get experiences:", error);
|
|
|
+ Toast.error("获取经验失败");
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
<header className={styles.topbar}>
|
|
|
<div className={styles.title}>
|
|
|
@@ -46,35 +156,70 @@ export const TopBar: FC<TopBarProps> = ({ onTraceSelect }) => {
|
|
|
<div className={styles.filters}>
|
|
|
<button
|
|
|
className={styles.button}
|
|
|
- onClick={() => console.log("新任务")}
|
|
|
+ onClick={handleNewTask}
|
|
|
>
|
|
|
新任务
|
|
|
</button>
|
|
|
<button
|
|
|
className={styles.buttonPrimary}
|
|
|
- onClick={() => console.log("运行")}
|
|
|
+ onClick={handleRun}
|
|
|
>
|
|
|
运行
|
|
|
</button>
|
|
|
<button
|
|
|
className={styles.buttonDanger}
|
|
|
- onClick={() => console.log("停止")}
|
|
|
+ onClick={handleStop}
|
|
|
>
|
|
|
停止
|
|
|
</button>
|
|
|
<button
|
|
|
className={styles.button}
|
|
|
- onClick={() => console.log("反思")}
|
|
|
+ onClick={handleReflect}
|
|
|
>
|
|
|
反思
|
|
|
</button>
|
|
|
<button
|
|
|
className={styles.button}
|
|
|
- onClick={() => console.log("经验")}
|
|
|
+ onClick={handleExperience}
|
|
|
>
|
|
|
经验
|
|
|
</button>
|
|
|
</div>
|
|
|
+ <Modal
|
|
|
+ title="新建任务"
|
|
|
+ visible={isModalVisible}
|
|
|
+ onOk={handleConfirm}
|
|
|
+ onCancel={() => setIsModalVisible(false)}
|
|
|
+ centered
|
|
|
+ style={{ width: 600 }}
|
|
|
+ >
|
|
|
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
|
+ <Form getFormApi={(api: any) => (formApiRef.current = api)}>
|
|
|
+ <Form.TextArea
|
|
|
+ field="system_prompt"
|
|
|
+ label="System Prompt"
|
|
|
+ placeholder="请输入 System Prompt"
|
|
|
+ autosize={{ minRows: 3, maxRows: 6 }}
|
|
|
+ />
|
|
|
+ <Form.TextArea
|
|
|
+ field="user_prompt"
|
|
|
+ label="User Prompt"
|
|
|
+ placeholder="请输入 User Prompt"
|
|
|
+ autosize={{ minRows: 3, maxRows: 6 }}
|
|
|
+ />
|
|
|
+ </Form>
|
|
|
+ </Modal>
|
|
|
+ <Modal
|
|
|
+ title="经验列表"
|
|
|
+ visible={isExperienceModalVisible}
|
|
|
+ onCancel={() => setIsExperienceModalVisible(false)}
|
|
|
+ footer={null}
|
|
|
+ centered
|
|
|
+ style={{ width: 800 }}
|
|
|
+ bodyStyle={{ maxHeight: "70vh", overflow: "auto" }}
|
|
|
+ >
|
|
|
+ <pre style={{ whiteSpace: "pre-wrap", wordWrap: "break-word" }}>{experienceContent || "暂无经验数据"}</pre>
|
|
|
+ </Modal>
|
|
|
</header>
|
|
|
);
|
|
|
};
|