Repository: giselles-ai/giselle
Pull request #798: Add reasoning styles
Author: login: kaochannel154
Created: 2025-05-01T11:57:49Z | Merged: 2025-05-09T02:00:40Z


## Summary
Improve UI styling for the Thinking Process display by removing unnecessary visual elements for better readability and cleaner appearance.
<img width="938" alt="スクリーンショット 2025-05-01 20 56 23" src="https://github.com/user-attachments/assets/3283454a-6b52-45d0-932f-1a5563c5707c" />

<img width="939" alt="スクリーンショット 2025-05-01 20 57 16" src="https://github.com/user-attachments/assets/1f406ae7-e55a-4876-8d91-5f05ed693f8b" />


<img width="939" alt="スクリーンショット 2025-05-01 20 57 24" src="https://github.com/user-attachments/assets/c8a1f168-935b-49ce-8f19-38032f0ab99c" />

## Related Issue
N/A

## Changes
- Improved styling for the Reasoning switch in the property panel
- Added alert for models that don't support reasoning
- Implemented progressive styling for Thinking Process display:

## Testing
- Tested the UI changes by confirming that:
  - Active reasoning still shows the Willi icon and curly brace with "Thinking..." text
  - Completed reasoning shows only "Thinking Process" text without icon or brace
  - Alert appears properly for non-supported models
  - Transitions between states work correctly
  - Property panel switch styling displays correctly

## Other Information
This change is part of ongoing UI improvements to make the reasoning feature more intuitive and visually appealing.


Diff:
diff --git a/internal-packages/workflow-designer-ui/src/editor/properties-panel/text-generation-node-properties-panel/model/anthropic.tsx b/internal-packages/workflow-designer-ui/src/editor/properties-panel/text-generation-node-properties-panel/model/anthropic.tsx
index 7e8830754a..716adb9401 100644
--- a/internal-packages/workflow-designer-ui/src/editor/properties-panel/text-generation-node-properties-panel/model/anthropic.tsx
+++ b/internal-packages/workflow-designer-ui/src/editor/properties-panel/text-generation-node-properties-panel/model/anthropic.tsx
@@ -5,7 +5,7 @@ import {
 	hasCapability,
 } from "@giselle-sdk/language-model";
 import { useUsageLimits } from "giselle-sdk/react";
-import { useMemo } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
 import {
 	Select,
 	SelectContent,
@@ -26,24 +26,43 @@ export function AnthropicModelPanel({
 	onModelChange: (changedValue: AnthropicLanguageModelData) => void;
 }) {
 	const limits = useUsageLimits();
-	const languageModel = useMemo(
-		() =>
-			anthropicLanguageModels.find((lm) => lm.id === anthropicLanguageModel.id),
-		[anthropicLanguageModel.id],
+
+	const hasReasoningCapability = useMemo(() => {
+		const languageModel = anthropicLanguageModels.find(
+			(lm) => lm.id === anthropicLanguageModel.id,
+		);
+		return languageModel && hasCapability(languageModel, Capability.Reasoning);
+	}, [anthropicLanguageModel.id]);
+
+	const handleModelChange = useCallback(
+		(value: string) => {
+			const newLanguageModel = anthropicLanguageModels.find(
+				(model) => model.id === value,
+			);
+			if (newLanguageModel === undefined) {
+				return;
+			}
+			onModelChange(
+				AnthropicLanguageModelData.parse({
+					...anthropicLanguageModel,
+					id: value,
+					configurations: {
+						...anthropicLanguageModel.configurations,
+						reasoning:
+							anthropicLanguageModel.configurations.reasoning &&
+							hasCapability(newLanguageModel, Capability.Reasoning),
+					},
+				}),
+			);
+		},
+		[anthropicLanguageModel, onModelChange],
 	);
 
 	return (
 		<div className="flex flex-col gap-[34px]">
 			<Select
 				value={anthropicLanguageModel.id}
-				onValueChange={(value) => {
-					onModelChange(
-						AnthropicLanguageModelData.parse({
-							...anthropicLanguageModel,
-							id: value,
-						}),
-					);
-				}}
+				onValueChange={handleModelChange}
 			>
 				<SelectTrigger>
 					<SelectValue placeholder="Select a LLM" />
@@ -103,28 +122,35 @@ export function AnthropicModelPanel({
 						}}
 					/>
 
-					<Switch
-						label="Reasoning"
-						name="reasoning"
-						checked={anthropicLanguageModel.configurations.reasoning}
-						onCheckedChange={(checked) => {
-							onModelChange(
-								AnthropicLanguageModelData.parse({
-									...anthropicLanguageModel,
-									configurations: {
-										...anthropicLanguageModel.configurations,
-										reasoning: checked,
-									},
-								}),
-							);
-						}}
-						note={
-							languageModel &&
-							anthropicLanguageModel.configurations.reasoning &&
-							!hasCapability(languageModel, Capability.Reasoning) &&
-							"Reasoning will not be used because the current model does not support it"
-						}
-					/>
+					{hasReasoningCapability ? (
+						<Switch
+							label="Reasoning"
+							name="reasoning"
+							checked={anthropicLanguageModel.configurations.reasoning}
+							onCheckedChange={(checked) => {
+								onModelChange(
+									AnthropicLanguageModelData.parse({
+										...anthropicLanguageModel,
+										configurations: {
+											...anthropicLanguageModel.configurations,
+											reasoning: checked,
+										},
+									}),
+								);
+							}}
+						/>
+					) : (
+						<>
+							{/* Refactor this because it duplicates the Switch component */}
+							<div className="flex flex-col">
+								<div className="flex flex-row items-center justify-between">
+									<p className="text-[14px]">Reasoning</p>
+									<div className="flex-grow mx-[12px] h-[1px] bg-black-200/30" />
+									<p className="text-[12px]">Unsuported</p>
+								</div>
+							</div>
+						</>
+					)}
 				</div>
 			</div>
 		</div>
diff --git a/internal-packages/workflow-designer-ui/src/ui/generation-view.tsx b/internal-packages/workflow-designer-ui/src/ui/generation-view.tsx
index bd5746b2c4..395c391dd0 100644
--- a/internal-packages/workflow-designer-ui/src/ui/generation-view.tsx
+++ b/internal-packages/workflow-designer-ui/src/ui/generation-view.tsx
@@ -86,12 +86,14 @@ export function GenerationView({
 											<Accordion.Item
 												value={`messages.${message.id}.parts.[${index}].reasoning`}
 											>
-												<Accordion.Trigger className="group text-white-400 text-[14px] flex items-center gap-[4px] cursor-pointer hover:text-white-800 transition-colors data-[state=open]:text-white-800 outline-none">
+												<Accordion.Trigger className="group text-white-400 text-[12px] flex items-center gap-[4px] cursor-pointer hover:text-white-800 transition-colors data-[state=open]:text-white-800 outline-none font-hubot">
 													<ChevronRightIcon
 														className="size-[16px] transition-transform duration-300 ease-[cubic-bezier(0.87,_0,_0.13,_1)] group-data-[state=open]:rotate-90"
 														aria-hidden
 													/>
-													Thinking
+													<span className="bg-[length:200%_100%] bg-clip-text bg-gradient-to-r from-[rgba(255,_255,_255,_1)] via-[rgba(255,_255,_255,_0.5)] to-[rgba(255,_255,_255,_1)] text-transparent animate-shimmer">
+														Thinking...
+													</span>
 												</Accordion.Trigger>
 												<Accordion.Content className="markdown-renderer overflow-hidden italic text-[14px] text-white-400 ml-[8px] pl-[12px] mb-[8px] data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown border-l border-l-white-400/20">
 													<MemoizedMarkdown content={part.reasoning} />
@@ -111,12 +113,12 @@ export function GenerationView({
 										<Accordion.Item
 											value={`messages.${message.id}.parts.[${index}].reason`}
 										>
-											<Accordion.Trigger className="group text-white-400 text-[14px] flex items-center gap-[4px] cursor-pointer hover:text-white-800 transition-colors data-[state=open]:text-white-800 outline-none">
+											<Accordion.Trigger className="group text-white-400 text-[12px] flex items-center gap-[4px] cursor-pointer hover:text-white-800 transition-colors data-[state=open]:text-white-800 outline-none font-hubot">
 												<ChevronRightIcon
 													className="size-[16px] transition-transform duration-300 ease-[cubic-bezier(0.87,_0,_0.13,_1)] group-data-[state=open]:rotate-90"
 													aria-hidden
 												/>
-												Thought Process
+												<span>Thinking Process</span>
 											</Accordion.Trigger>
 											<Accordion.Content className="markdown-renderer overflow-hidden italic text-[14px] text-white-400 ml-[8px] pl-[12px] mb-[8px] data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown border-l border-l-white-400/20">
 												<MemoizedMarkdown content={part.reasoning} />
@@ -155,7 +157,14 @@ export function GenerationView({
 				</div>
 			))}
 			{generation.status !== "completed" &&
-				generation.status !== "cancelled" && (
+				generation.status !== "cancelled" &&
+				// reasoning部分がない場合のみSpinnerを表示
+				!generatedMessages.some((message) =>
+					message.parts?.some(
+						(part) =>
+							part.type === "reasoning" && generation.status === "running",
+					),
+				) && (
 					<div className="pt-[8px]">
 						<Spinner />
 					</div>
diff --git a/internal-packages/workflow-designer-ui/src/ui/switch.tsx b/internal-packages/workflow-designer-ui/src/ui/switch.tsx
index de5063b026..4d41dd31a3 100644
--- a/internal-packages/workflow-designer-ui/src/ui/switch.tsx
+++ b/internal-packages/workflow-designer-ui/src/ui/switch.tsx
@@ -16,27 +16,33 @@ export const Switch = ({
 	note?: ReactNode;
 }) => (
 	<div className="flex flex-col">
-		<label className="text-[14px] py-[1.5px]" htmlFor={name}>
-			{label}
-		</label>
-		<RadixSwitch.Root
-			className={clsx(
-				"h-[15px] w-[27px] rounded-full outline-none",
-				"border border-black-300 data-[state=checked]:border-primary-900",
-				"bg-transparent data-[state=checked]:bg-primary-900",
-			)}
-			id={name}
-			checked={checked}
-			onCheckedChange={onCheckedChange}
-		>
-			<RadixSwitch.Thumb
+		<div className="flex flex-row items-center justify-between">
+			<label className="text-[14px]" htmlFor={name}>
+				{label}
+			</label>
+
+			{/* 区切り線 */}
+			<div className="flex-grow mx-[12px] h-[1px] bg-black-200/30" />
+
+			<RadixSwitch.Root
 				className={clsx(
-					"block size-[11px] translate-x-[1px] rounded-full",
-					"bg-black-300 data-[state=checked]:bg-white-900",
-					"transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[13px]",
+					"h-[15px] w-[27px] rounded-full outline-none",
+					"border border-white-400 data-[state=checked]:border-primary-900",
+					"bg-transparent data-[state=checked]:bg-primary-900",
 				)}
-			/>
-		</RadixSwitch.Root>
-		{note && <p className="text-[14px] text-black-200">{note}</p>}
+				id={name}
+				checked={checked}
+				onCheckedChange={onCheckedChange}
+			>
+				<RadixSwitch.Thumb
+					className={clsx(
+						"block size-[11px] translate-x-[2px] rounded-full",
+						"bg-white-400 data-[state=checked]:bg-white-900",
+						"transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[13px]",
+					)}
+				/>
+			</RadixSwitch.Root>
+		</div>
+		{note && <p className="text-[12px] text-red-900 mt-[4px]">{note}</p>}
 	</div>
 );


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR improves the UI and UX of the reasoning/Thinking Process feature by updating the switch styling, refining the reasoning display in the generation view, and adding support for handling models that do not support reasoning.  
- Updated Switch component layout and styling  
- Adjusted reasoning display and accordion triggers in generation view  
- Enhanced model panel behavior to provide error feedback for unsupported reasoning

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

| File | Description |
| ---- | ----------- |
| internal-packages/workflow-designer-ui/src/ui/switch.tsx | Redesigned the switch layout with a divider and updated styling properties |
| internal-packages/workflow-designer-ui/src/ui/generation-view.tsx | Modified the reasoning accordion trigger and conditional rendering based on the model's running status |
| internal-packages/workflow-designer-ui/src/editor/properties-panel/text-generation-node-properties-panel/model/anthropic.tsx | Added internal state and error handling for unsupported reasoning on specific models |


<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**internal-packages/workflow-designer-ui/src/ui/generation-view.tsx:78**
* Verify that adding the isRunning condition here is an intentional design decision, ensuring that the reasoning accordion only renders for active reasoning sessions and not for completed processes.
```
if (lastPart && isRunning) {
```
**internal-packages/workflow-designer-ui/src/ui/switch.tsx:25**
* [nitpick] Confirm that the divider's margin and color values align with the overall design system, as the switch styling now relies on these hardcoded values for visual separation.
```
<div className="flex-grow mx-[12px] h-[1px] bg-black-200/30"></div>
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T11:58:33Z
user: login: shige
body: Thank you!! 💭 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-07T08:41:02Z
user: login: toyamarinyon
body: I think there are two things we should consider.

1. It feels awkward when discrete animations and continuous animations are mixed within a single sequence.
   
   After pressing the Generate button, the animation where three Ghosts appear in a row moves discretely with a blinking effect. This is very cute.
   
   Next, when Thinking begins, it displays with a shimmer effect. It's very beautiful. The shimmer representation of thinking is commonly seen, so it feels natural.
   
   I think both animations are well made on their own, but when combined, they seem a bit disjointed.
   
   It's like starting with R&B and suddenly switching to a ballad. While you could say that's what aespa does, it would be surprising if aespa appeared here. That kind of thing stands out because it's on the center stage. I think in these cases, not changing the rhythm of animations too much creates a more natural and comfortable experience.
   
   Similarly with the Cursor, the `...` is a discrete animation and Thinking is a shimmer, but I didn't feel any awkwardness because they appear in different positions. Perhaps we feel awkwardness when animations displayed in the same position change rhythm.

    https://github.com/user-attachments/assets/32fc5da5-d47d-435b-a9de-9802d13ce2f3

2. It might be time to give meaning to the characters
   
   Until now, we've been using the ghost characters as a sort of design accent without much thought. We haven't really delved into it, but it might be time to consider it. This is because when the ghost appears during Thinking, I had the impression that it was doing the thinking. I don't know if it's one of the LLMs or something that manages them, but I think we need some kind of setting for this.
   
   Personally, I think we can express it more freely if it's a separate entity from the functionality.


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T06:44:02Z
user: login: toyamarinyon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T07:50:33Z
user: login: toyamarinyon
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T01:59:47Z

Review comments:
diff_hunk: @@ -31,6 +31,30 @@ export function AnthropicModelPanel({
 			anthropicLanguageModels.find((lm) => lm.id === anthropicLanguageModel.id),
 		[anthropicLanguageModel.id],
 	);
+	
+	const supportsReasoning = languageModel && hasCapability(languageModel, Capability.Reasoning);
+	
+	// UIのスイッチ状態を追跡するための内部ステート
+	const [reasoningEnabled, setReasoningEnabled] = useState(
+		supportsReasoning ? anthropicLanguageModel.configurations.reasoning : false
+	);
+	
+	// エラーメッセージ表示用の状態
+	const [showError, setShowError] = useState(false);
+	const timeoutRef = useRef<NodeJS.Timeout | null>(null);
+	
+	// モデルまたは設定が変更されたときにUIの状態を更新
+	useEffect(() => {
path: internal-packages/workflow-designer-ui/src/editor/properties-panel/text-generation-node-properties-panel/model/anthropic.tsx
user: login: Copilot
body: Consider adding a cleanup function in this useEffect to clear timeoutRef (e.g., by returning a cleanup function) to avoid potential memory leaks if the component unmounts before the timeout fires.
created_at: 2025-05-01T11:58:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,9 +72,10 @@ export function GenerationView({
 				<div key={message.id}>
 					{message.parts?.map((part, index) => {
 						const lastPart = message.parts?.length === index + 1;
+						const isRunning = generation.status === "running";
 						switch (part.type) {
 							case "reasoning":
-								if (lastPart) {
+								if (lastPart && isRunning) {
path: internal-packages/workflow-designer-ui/src/ui/generation-view.tsx
user: login: toyamarinyon
body: Why do we need consider `isRunning` ?
created_at: 2025-05-08T07:42:36Z
author_association: CONTRIBUTOR
path: internal-packages/workflow-designer-ui/src/editor/properties-panel/text-generation-node-properties-panel/model/anthropic.tsx
user: login: toyamarinyon
body: This implementation is too complicated for what I want to do, so I'll create a pull request to rewrite it a bit.
created_at: 2025-05-08T07:50:25Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-01T11:57:53Z
body: ###  ⚠️  No Changeset found

Latest commit: 0ba0999ed0e26585206f3a70ad7644b81c722b6c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**



[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/giselles-ai/giselle/new/add-reasoning-styles?filename=.changeset/afraid-meals-cheat.md&value=---%0A%22%40fake-scope%2Ffake-pkg%22%3A%20patch%0A---%0A%0AAdd%20reasoning%20styles%0A)

<details><summary>💥 An error occurred when fetching the changed packages and changesets in this PR</summary>

```
Some errors occurred when validating the changesets config:
The package "playground" depends on the ignored package "giselle-sdk", but "playground" is not being ignored. Please add "playground" to the `ignore` option.
The package "studio.giselles.ai" depends on the ignored package "giselle-sdk", but "studio.giselles.ai" is not being ignored. Please add "studio.giselles.ai" to the `ignore` option.
The package "@giselle-internal/workflow-designer-ui" depends on the ignored package "giselle-sdk", but "@giselle-internal/workflow-designer-ui" is not being ignored. Please add "@giselle-internal/workflow-designer-ui" to the `ignore` option.
```

</details>

author_association: NONE
user: login: vercel[bot]
created_at: 2025-05-01T11:57:53Z
body: [vc]: #PKkjVCBZfyMLzDSY7Pecw7xtMuj4jk8dK1eNDIQSlfk=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJnaXNlbGxlIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvc3R1ZGlvLmdpc2VsbGVzLmFpIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6Imdpc2VsbGUtZ2l0LWFkZC1yZWFzb25pbmctc3R5bGVzLXIwNi1lZGdlLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3IwNi1lZGdlL2dpc2VsbGUvOVY0VTNKZlBWcmNtUFZKV0F5TEhYNHFlaWZXRyIsInByZXZpZXdVcmwiOiJnaXNlbGxlLWdpdC1hZGQtcmVhc29uaW5nLXN0eWxlcy1yMDYtZWRnZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIn0seyJuYW1lIjoiZ2lzZWxsZS1wbGF5Z3JvdW5kIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvcGxheWdyb3VuZCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJnaXNlbGxlLXBsYXlncm91bmQtZ2l0LWFkZC1yZWFzb25pbmctc3R5bGVzLXIwNi1lZGdlLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3IwNi1lZGdlL2dpc2VsbGUtcGxheWdyb3VuZC8yOGhQU25UVGNQNkNpNHo0ekUyUU5GTnY5TVlEIiwicHJldmlld1VybCI6Imdpc2VsbGUtcGxheWdyb3VuZC1naXQtYWRkLXJlYXNvbmluZy1zdHlsZXMtcjA2LWVkZ2UudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **giselle** | ✅ Ready ([Inspect](https://vercel.com/r06-edge/giselle/9V4U3JfPVrcmPVJWAyLHX4qeifWG)) | [Visit Preview](https://giselle-git-add-reasoning-styles-r06-edge.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/giselle-git-add-reasoning-styles-r06-edge.vercel.app?via=pr-comment-feedback-link) | May 9, 2025 1:58am |
| **giselle-playground** | ✅ Ready ([Inspect](https://vercel.com/r06-edge/giselle-playground/28hPSnTTcP6Ci4z4zE2QNFNv9MYD)) | [Visit Preview](https://giselle-playground-git-add-reasoning-styles-r06-edge.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/giselle-playground-git-add-reasoning-styles-r06-edge.vercel.app?via=pr-comment-feedback-link) | May 9, 2025 1:58am |



author_association: NONE
user: login: toyamarinyon
created_at: 2025-05-01T12:14:51Z
body: Great!!!!
author_association: CONTRIBUTOR
user: login: toyamarinyon
created_at: 2025-05-07T09:00:15Z
body: Sorry. I'll review later. We should think when and how to use ghost icon
author_association: CONTRIBUTOR