feat(FN-1740): merge fusion/fn-1740
This commit is contained in:
@@ -353,8 +353,8 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo
|
||||
async (value: string) => {
|
||||
const requestTaskId = task.id;
|
||||
const previousThinking = savedThinking;
|
||||
// Value "off" means clear override (null)
|
||||
const nextValue = value === "off" ? null : value;
|
||||
// Empty string means clear override (null)
|
||||
const nextValue = value === "" ? null : value;
|
||||
|
||||
setSelectedThinking(nextValue);
|
||||
setSavingTarget("thinking");
|
||||
@@ -547,12 +547,13 @@ export function ModelSelectorTab({ task, addToast, onTaskUpdated, settings }: Mo
|
||||
</div>
|
||||
<select
|
||||
id="thinkingLevel"
|
||||
value={selectedThinking ?? "off"}
|
||||
value={selectedThinking ?? ""}
|
||||
onChange={(e) => handleThinkingChange(e.target.value)}
|
||||
disabled={isSaving}
|
||||
className="thinking-level-select"
|
||||
>
|
||||
<option value="off">Off (default)</option>
|
||||
<option value="">Default ({settings?.defaultThinkingLevel ?? "off"})</option>
|
||||
<option value="off">Off</option>
|
||||
<option value="minimal">Minimal</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
|
||||
const [executorModel, setExecutorModel] = useState("");
|
||||
const [validatorModel, setValidatorModel] = useState("");
|
||||
const [planningModel, setPlanningModel] = useState("");
|
||||
const [thinkingLevel, setThinkingLevel] = useState<string>("off");
|
||||
const [thinkingLevel, setThinkingLevel] = useState<string>("");
|
||||
const [selectedPresetId, setSelectedPresetId] = useState<string>("");
|
||||
const [presetMode, setPresetMode] = useState<"default" | "preset" | "custom">("default");
|
||||
const [hasDirtyState, setHasDirtyState] = useState(false);
|
||||
@@ -94,7 +94,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
|
||||
executorModel !== "" ||
|
||||
validatorModel !== "" ||
|
||||
planningModel !== "" ||
|
||||
thinkingLevel !== "off" ||
|
||||
thinkingLevel !== "" ||
|
||||
selectedWorkflowSteps.length > 0 ||
|
||||
selectedAgentId !== null;
|
||||
setHasDirtyState(isDirty);
|
||||
@@ -113,7 +113,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
|
||||
setExecutorModel("");
|
||||
setValidatorModel("");
|
||||
setPlanningModel("");
|
||||
setThinkingLevel("off");
|
||||
setThinkingLevel("");
|
||||
setSelectedPresetId("");
|
||||
setPresetMode("default");
|
||||
setSelectedWorkflowSteps([]);
|
||||
@@ -150,7 +150,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
|
||||
validatorModelId: validatorModel && validatorSlashIdx !== -1 ? validatorModel.slice(validatorSlashIdx + 1) : undefined,
|
||||
planningModelProvider: planningModel && planningSlashIdx !== -1 ? planningModel.slice(0, planningSlashIdx) : undefined,
|
||||
planningModelId: planningModel && planningSlashIdx !== -1 ? planningModel.slice(planningSlashIdx + 1) : undefined,
|
||||
thinkingLevel: thinkingLevel !== "off" ? thinkingLevel as "minimal" | "low" | "medium" | "high" : undefined,
|
||||
thinkingLevel: thinkingLevel !== "" ? thinkingLevel as "minimal" | "low" | "medium" | "high" : undefined,
|
||||
});
|
||||
|
||||
// Upload pending images as attachments
|
||||
@@ -176,7 +176,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
|
||||
setExecutorModel("");
|
||||
setValidatorModel("");
|
||||
setPlanningModel("");
|
||||
setThinkingLevel("off");
|
||||
setThinkingLevel("");
|
||||
setSelectedPresetId("");
|
||||
setPresetMode("default");
|
||||
setSelectedWorkflowSteps([]);
|
||||
|
||||
@@ -294,6 +294,7 @@ export function TaskDetailModal({
|
||||
const [editExecutorModel, setEditExecutorModel] = useState("");
|
||||
const [editValidatorModel, setEditValidatorModel] = useState("");
|
||||
const [editPlanningModel, setEditPlanningModel] = useState("");
|
||||
const [editThinkingLevel, setEditThinkingLevel] = useState("");
|
||||
const [editPresetMode, setEditPresetMode] = useState<"default" | "preset" | "custom">("default");
|
||||
const [editSelectedPresetId, setEditSelectedPresetId] = useState("");
|
||||
const [editSelectedWorkflowSteps, setEditSelectedWorkflowSteps] = useState<string[]>(task.enabledWorkflowSteps || []);
|
||||
@@ -468,6 +469,7 @@ export function TaskDetailModal({
|
||||
setEditExecutorModel(execModel);
|
||||
setEditValidatorModel(valModel);
|
||||
setEditPlanningModel(planModel);
|
||||
setEditThinkingLevel(task.thinkingLevel ?? "");
|
||||
setEditPresetMode(execModel || valModel || planModel ? "custom" : "default");
|
||||
setEditSelectedPresetId("");
|
||||
setEditSelectedWorkflowSteps(task.enabledWorkflowSteps || []);
|
||||
@@ -502,6 +504,7 @@ export function TaskDetailModal({
|
||||
validatorModelId: editValidatorModel && validatorSlashIdx !== -1 ? editValidatorModel.slice(validatorSlashIdx + 1) : null,
|
||||
planningModelProvider: editPlanningModel && planningSlashIdx !== -1 ? editPlanningModel.slice(0, planningSlashIdx) : null,
|
||||
planningModelId: editPlanningModel && planningSlashIdx !== -1 ? editPlanningModel.slice(planningSlashIdx + 1) : null,
|
||||
thinkingLevel: editThinkingLevel !== "" ? (editThinkingLevel as "minimal" | "low" | "medium" | "high") : null,
|
||||
};
|
||||
|
||||
await updateTask(task.id, updates, projectId);
|
||||
@@ -534,7 +537,7 @@ export function TaskDetailModal({
|
||||
setIsSaving(false);
|
||||
}
|
||||
}
|
||||
}, [task.id, editTitle, editDescription, editDependencies, editExecutorModel, editValidatorModel, editPlanningModel, editSelectedWorkflowSteps, editPendingImages, addToast, projectId]);
|
||||
}, [task.id, editTitle, editDescription, editDependencies, editExecutorModel, editValidatorModel, editPlanningModel, editThinkingLevel, editSelectedWorkflowSteps, editPendingImages, addToast, projectId]);
|
||||
|
||||
const handleAutoSaveDescription = useCallback(async (description: string) => {
|
||||
try {
|
||||
@@ -1040,6 +1043,8 @@ export function TaskDetailModal({
|
||||
onValidatorModelChange={setEditValidatorModel}
|
||||
planningModel={editPlanningModel}
|
||||
onPlanningModelChange={setEditPlanningModel}
|
||||
thinkingLevel={editThinkingLevel}
|
||||
onThinkingLevelChange={setEditThinkingLevel}
|
||||
presetMode={editPresetMode}
|
||||
onPresetModeChange={setEditPresetMode}
|
||||
selectedPresetId={editSelectedPresetId}
|
||||
|
||||
@@ -109,7 +109,7 @@ export function TaskForm({
|
||||
executorModel !== "" ||
|
||||
validatorModel !== "" ||
|
||||
(planningModel || "") !== "" ||
|
||||
(thinkingLevel || "off") !== "off";
|
||||
(thinkingLevel || "") !== "";
|
||||
|
||||
const [showDepDropdown, setShowDepDropdown] = useState(false);
|
||||
const [showMoreOptions, setShowMoreOptions] = useState(hasInitialMoreOptions);
|
||||
@@ -169,7 +169,7 @@ export function TaskForm({
|
||||
executorModel !== "" ||
|
||||
validatorModel !== "" ||
|
||||
(planningModel || "") !== "" ||
|
||||
(thinkingLevel || "off") !== "off";
|
||||
(thinkingLevel || "") !== "";
|
||||
|
||||
// Auto-select preset by size (create mode only)
|
||||
useEffect(() => {
|
||||
@@ -926,11 +926,12 @@ export function TaskForm({
|
||||
<label htmlFor="thinking-level" className="model-select-label">Thinking</label>
|
||||
<select
|
||||
id="thinking-level"
|
||||
value={thinkingLevel || "off"}
|
||||
value={thinkingLevel || ""}
|
||||
onChange={(e) => onThinkingLevelChange(e.target.value)}
|
||||
disabled={disabled || presetMode === "preset"}
|
||||
>
|
||||
<option value="off">Off (default)</option>
|
||||
<option value="">Default ({settings?.defaultThinkingLevel ?? "off"})</option>
|
||||
<option value="off">Off</option>
|
||||
<option value="minimal">Minimal</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
|
||||
@@ -990,14 +990,14 @@ describe("ModelSelectorTab", () => {
|
||||
});
|
||||
|
||||
describe("thinkingLevel selector", () => {
|
||||
it("renders thinking level selector with default 'off'", async () => {
|
||||
it("renders thinking level selector with empty string default", async () => {
|
||||
render(<ModelSelectorTab task={FAKE_TASK} addToast={mockAddToast} />);
|
||||
|
||||
await waitForSelectors();
|
||||
|
||||
const select = screen.getByLabelText("Thinking Level");
|
||||
expect(select).toBeInTheDocument();
|
||||
expect((select as HTMLSelectElement).value).toBe("off");
|
||||
expect((select as HTMLSelectElement).value).toBe("");
|
||||
});
|
||||
|
||||
it("renders current thinking level from task", async () => {
|
||||
@@ -1068,7 +1068,8 @@ describe("ModelSelectorTab", () => {
|
||||
|
||||
await waitForSelectors();
|
||||
|
||||
await user.selectOptions(screen.getByLabelText("Thinking Level"), "off");
|
||||
// Select the "Default (...)" option (empty string) to clear the override
|
||||
await user.selectOptions(screen.getByLabelText("Thinking Level"), "");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
@@ -1145,7 +1146,8 @@ describe("ModelSelectorTab", () => {
|
||||
|
||||
await waitForSelectors();
|
||||
|
||||
await user.selectOptions(screen.getByLabelText("Thinking Level"), "off");
|
||||
// Select the "Default (...)" option (empty string) to clear the override
|
||||
await user.selectOptions(screen.getByLabelText("Thinking Level"), "");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
@@ -1182,7 +1184,8 @@ describe("ModelSelectorTab", () => {
|
||||
|
||||
await waitForSelectors();
|
||||
|
||||
await user.selectOptions(screen.getByLabelText("Thinking Level"), "off");
|
||||
// Select the "Default (...)" option (empty string) to clear the override
|
||||
await user.selectOptions(screen.getByLabelText("Thinking Level"), "");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
@@ -1191,5 +1194,34 @@ describe("ModelSelectorTab", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("saves 'off' explicitly as a real override when selected", async () => {
|
||||
const taskWithThinking = { ...FAKE_TASK, thinkingLevel: "high" as const };
|
||||
mockUpdateTask.mockImplementation(async (_id: string, updates: Record<string, unknown>) => ({
|
||||
...FAKE_TASK,
|
||||
...updates,
|
||||
}));
|
||||
|
||||
const user = userEvent.setup();
|
||||
render(<ModelSelectorTab task={taskWithThinking} addToast={mockAddToast} />);
|
||||
|
||||
await waitForSelectors();
|
||||
|
||||
// Select "off" explicitly (not the default clear option)
|
||||
await user.selectOptions(screen.getByLabelText("Thinking Level"), "off");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
thinkingLevel: "off",
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddToast).toHaveBeenCalledWith(
|
||||
"Thinking level set to off",
|
||||
"success",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1412,9 +1412,7 @@ export class TaskExecutor {
|
||||
: settings.defaultModelId;
|
||||
const executorFallbackProvider = settings.fallbackProvider;
|
||||
const executorFallbackModelId = settings.fallbackModelId;
|
||||
const executorThinkingLevel = detail.thinkingLevel
|
||||
? detail.thinkingLevel
|
||||
: settings.defaultThinkingLevel;
|
||||
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
||||
|
||||
// Determine whether we're resuming a previous session (pause/resume)
|
||||
// or starting fresh. Use file-based sessions so conversation state
|
||||
|
||||
Reference in New Issue
Block a user