Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions examples/ui/chat-monitor/workflow-popup/workflow-popup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import blessed from "neo-blessed";
import { join } from "path";
import { WorkflowPopup } from "../../../../src/ui/chat-monitor/workflow-popup/workflow-popup.js";
import { ControlsManager } from "../../../../src/ui/controls/controls-manager.js";
import { keyActionListenerFactory } from "../../../../src/ui/controls/key-bindings.js";
import {
NavigationDescription,
NavigationDirection,
} from "../../../../src/ui/controls/navigation.js";
import { HelpBar } from "../../../../src/ui/shared/help-bar.js";
import { getLogger } from "../../helpers/log.js";

const logger = getLogger(true);

// // Mocking the file system for testing purposes
// mock({
// '/tmp/project': {
// 'workflow_state.log': mock.load('./state.log') // can copy real files
// }
// });

const screen = blessed.screen({ title: "Workflow" });
const controlsManager = new ControlsManager(screen, logger);
controlsManager.updateKeyActions(controlsManager.screen.id, {
kind: "exclusive",
actions: [
{
key: "C-c",
action: {
description: NavigationDescription.EXIT_APP,
listener: keyActionListenerFactory(() => {
process.exit(0);
}),
},
},
{
key: "enter",
action: {
description: NavigationDescription.IN_OUT,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.IN);
}),
},
},
{
key: "escape",
action: {
description: NavigationDescription.IN_OUT,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.OUT);
}),
},
},
{
key: "left",
action: {
description: NavigationDescription.LEFT_RIGHT,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.LEFT);
}),
},
},
{
key: "right",
action: {
description: NavigationDescription.LEFT_RIGHT,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.RIGHT);
}),
},
},
{
key: "up",
action: {
description: NavigationDescription.UP_DOWN,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.UP);
}),
},
},
{
key: "down",
action: {
description: NavigationDescription.UP_DOWN,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.DOWN);
}),
},
},
{
key: "tab",
action: {
description: NavigationDescription.NEXT_PREV,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.NEXT);
}),
},
},
{
key: "S-tab",
action: {
description: NavigationDescription.NEXT_PREV,
listener: keyActionListenerFactory(() => {
controlsManager.navigate(NavigationDirection.PREVIOUS);
}),
},
},
],
});

new HelpBar(
{
kind: "parent",
parent: controlsManager.screen,
controlsManager,
},
logger,
);

const workflow = new WorkflowPopup(
{
kind: "parent",
parent: controlsManager.screen,
controlsManager,
},
logger,
join(
process.cwd(),
"examples",
"ui",
"chat-monitor",
"workflow-popup",
"state.log",
), // Path to the workflow state log file
);
workflow.show(controlsManager.screen.id);
29 changes: 29 additions & 0 deletions src/ui/chat-monitor/chat-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ import { ChatInput } from "./input/input.js";
import { ChatRuntimeHandler, MessageTypeEnum } from "./runtime-handler.js";
import { Runtime } from "@/runtime/index.js";
import { isNonNullish } from "remeda";
import { WorkflowPopup } from "./workflow-popup/workflow-popup.js";
import { join } from "path";

export class ChatMonitor extends ContainerComponent {
private closeDialog: CloseDialog;
private filter: ChatFilter;
private messages: Messages;
private helpBar: HelpBar;
private chatInput: ChatInput;
private workflowPopup: WorkflowPopup;

private runtimeHandler: ChatRuntimeHandler;
private _isProcessing = false;
Expand Down Expand Up @@ -87,6 +90,20 @@ export class ChatMonitor extends ContainerComponent {
logger,
);

this.workflowPopup = new WorkflowPopup(
{
kind: "parent",
parent: this.parent,
controlsManager: this.controlsManager,
// join(dirPath ?? process.cwd(), "state", "task_state.log");
// onAutoPopup: () => {
// this.workflowPopup.show(this.controlsManager.focused.id);
// },
},
logger,
join("./output", "state", "workflow_state.log"),
);

// Should be last to appear on top
this.closeDialog = new CloseDialog(this.controlsManager);
this.setupEventHandlers();
Expand Down Expand Up @@ -221,6 +238,16 @@ export class ChatMonitor extends ContainerComponent {
}),
},
},
{
key: "C-w",
action: {
description: NavigationDescription.WORKFLOW_EXPLORER,
listener: keyActionListenerFactory(() => {
this.collapse();
this.workflowPopup.show(this.controlsManager.focused.id);
}),
},
},
].filter(isNonNullish),
});

Expand Down Expand Up @@ -254,6 +281,7 @@ export class ChatMonitor extends ContainerComponent {
private collapse() {
this.filter.collapse();
this.closeDialog.hide();
this.workflowPopup.hide();
}

private setupEventHandlers() {
Expand Down Expand Up @@ -317,6 +345,7 @@ export class ChatMonitor extends ContainerComponent {

private abortOperation(onAbort?: () => void) {
if (!this._isProcessing || this._isAborting) {
this.onAbort?.();
return;
}
this.onAbort = onAbort;
Expand Down
123 changes: 82 additions & 41 deletions src/ui/chat-monitor/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { updateDeepPartialObject } from "@/utils/objects.js";
import { clone } from "remeda";
import { UIColors } from "../colors.js";
import * as st from "../config.js";
Expand Down Expand Up @@ -99,59 +100,89 @@ export function formatCompleteMessage(
return `${formattedTimestamp} ${formattedRole}: ${formattedContent}`;
}

export function getBaseStyle(override?: any) {
return updateDeepPartialObject<any>(
{
style: {
bg: st.UIConfig.colors.bg,
fg: st.UIConfig.colors.fg,
focus: {
bg: st.UIConfig.colors.bg,
},
},
},
override,
);
}

export function getTextFieldStyle(override?: any) {
return updateDeepPartialObject<any>(getBaseStyle(), override);
}

export function getBorderedBoxStyle(active = false, override?: any) {
const out = updateDeepPartialObject<any>(
getBaseStyle(),
updateDeepPartialObject<any>(
{
border: {
type: "line",
bg: st.UIConfig.colors.bg,
fg: active ? st.UIConfig.colors.active : st.UIConfig.colors.fg,
},
style: {
label: {
fg: st.UIConfig.colors.fg,
bg: st.UIConfig.colors.bg,
},
focus: {
border: {
fg: active
? st.UIConfig.colors.active
: st.UIConfig.colors.focused,
bg: st.UIConfig.colors.bg,
},
},
},
},
override,
),
);

return clone(out);
}

/**
* Get UI styling for the messages box
* @returns Object with UI configuration for the messages box
*/
export function getMessagesContainerStyle() {
const border = clone(st.UIConfig.borders.general) as any;
export function getMessagesContainerStyle(active = false) {
return {
border,
...getBorderedBoxStyle(active),
label: " Messages ",
style: {
focus: border.focus,
},
};
}

export function getMessagesBoxStyle() {
return {
return updateDeepPartialObject<any>(getBaseStyle(), {
scrollbar: st.UIConfig.scrollbar,
style: {
focus: {
bg: UIColors.green.dartmouth_green,
},
},
};
});
}

export function getInputContainerBoxStyle() {
const border = clone(st.UIConfig.borders.general) as any;
export function getInputContainerBoxStyle(active = false) {
return {
border: border,
...getBorderedBoxStyle(active),
label: " Input ",
style: {
focus: border.focus,
},
};
}

export function getInputBoxStyle() {
return {
style: {
...st.UIConfig.input,
},
};
return updateDeepPartialObject<any>(getBaseStyle(), {
scrollbar: st.UIConfig.scrollbar,
});
}

/**
* Get UI styling for the abort button
* @param isAbort Whether the system is currently processing
* @returns Object with UI configuration for the abort button
*/

export function getButtonStyle(disabled = false) {
return {
return updateDeepPartialObject<any>(getBaseStyle(), {
align: "center" as any,
valign: "middle" as any,
style: {
Expand All @@ -161,7 +192,7 @@ export function getButtonStyle(disabled = false) {
bg: disabled ? UIColors.red.cardinal : UIColors.blue.electric_blue,
},
},
};
});
}

export function getSendButtonStyle(disabled = false) {
Expand All @@ -171,21 +202,31 @@ export function getSendButtonStyle(disabled = false) {
};
}
export function getAbortButtonStyle(disabled = false) {
return {
return updateDeepPartialObject<any>(getButtonStyle(disabled), {
content: "ABORT",
...getButtonStyle(disabled),
style: {
...getButtonStyle(disabled).style,
bg: disabled ? UIColors.gray.cool_gray : UIColors.red.dark_red,
focus: {
bg: disabled ? UIColors.gray.cool_gray : UIColors.red.electric_red,
},
},
};
});
}
export function getHideButtonStyle(disabled = false) {
return {
content: "Hide",
...getButtonStyle(disabled),
};

export function getCheckboxStyle(checked: boolean, override?: any) {
return updateDeepPartialObject<any>(
getTextFieldStyle({
style: {
focus: {
fg: st.UIConfig.colors.focused,
},
},
}),
updateDeepPartialObject<any>(
{
checked: checked,
},
override,
),
);
}
Loading