Skip to content

Commit 44959d1

Browse files
committedJan 21, 2025·
<think> tag support for reasoning models
1 parent c168bb0 commit 44959d1

13 files changed

+312
-248
lines changed
 

‎client/src/components/ChatNode.tsx

+61-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
SelectTrigger,
1919
SelectValue,
2020
} from "@/components/ui/select";
21-
import { AIModel, APIResponseMetrics, CustomModel, Message, availableModels } from "@/lib/types";
21+
import { AIModel, APIResponseMetrics, CustomModel, Message} from "@/lib/types";
22+
import { availableModels } from "@/lib/remotemodels";
2223
import { cn, isEqual, sanitizeChatMessages } from "@/lib/utils";
2324
import { useStore } from "@/lib/store";
2425
import { Copy, Check } from "lucide-react";
@@ -34,6 +35,8 @@ import { CodeBlock } from "./CodeBlock";
3435
import remarkGfm from 'remark-gfm';
3536
import { countTokens } from "@/lib/toksec";
3637
import { useMetricsStore } from "@/lib/metricstore";
38+
import { processThinkingContent } from "@/lib/utils";
39+
import { ThinkingBlock } from "./ThinkingBlock";
3740

3841
export function ChatNode({ id, data: initialData }: NodeProps) {
3942
const [input, setInput] = useState("");
@@ -848,6 +851,58 @@ export function ChatNode({ id, data: initialData }: NodeProps) {
848851
className="max-w-sm rounded-md"
849852
/>
850853
)}
854+
{msg.content && (() => {
855+
const { blocks, processedContent } = processThinkingContent(msg.content);
856+
return (
857+
<>
858+
{blocks.map((block) =>
859+
block.type === 'thinking' ? (
860+
<ThinkingBlock key={block.key}>
861+
<ReactMarkdown
862+
components={{
863+
code: CodeBlock as any,
864+
pre: ({ children }) => <pre className="p-0 m-0" onClick={e => e.stopPropagation()}>{children}</pre>,
865+
h1: ({ children }) => <h1 className="text-2xl font-bold mb-2">{children}</h1>,
866+
h2: ({ children }) => <h2 className="text-xl font-bold mb-2">{children}</h2>,
867+
h3: ({ children }) => <h3 className="text-lg font-bold mb-2">{children}</h3>,
868+
h4: ({ children }) => <h4 className="text-base font-bold mb-2">{children}</h4>,
869+
p: ({ children }) => <p className="mt-1 mb-2">{children}</p>,
870+
ul: ({ children }) => <ul className="list-disc list-inside mb-4">{children}</ul>,
871+
ol: ({ children }) => <ol className="list-decimal list-inside mb-4">{children}</ol>,
872+
li: ({ children }) => <li className="mb-2">{children}</li>,
873+
a: ({ href, children }) => (
874+
<a href={href} className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">
875+
{children}
876+
</a>
877+
),
878+
blockquote: ({ children }) => (
879+
<blockquote className="border-l-4 border-primary pl-4 italic mb-4">
880+
{children}
881+
</blockquote>
882+
),
883+
img: ({ src, alt }) => (
884+
<img src={src} alt={alt} className="max-w-full h-auto rounded-lg mb-4" />
885+
),
886+
table: ({ children }) => (
887+
<div className="overflow-x-auto mb-4">
888+
<table className="min-w-full divide-y divide-border">
889+
{children}
890+
</table>
891+
</div>
892+
),
893+
th: ({ children }) => (
894+
<th className="px-4 py-2 bg-muted font-medium">{children}</th>
895+
),
896+
td: ({ children }) => (
897+
<td className="px-4 py-2 border-t">{children}</td>
898+
),
899+
}}
900+
remarkPlugins={[remarkGfm]}>
901+
{block.content}
902+
</ReactMarkdown>
903+
</ThinkingBlock>
904+
) : null
905+
)}
851906
<ReactMarkdown
852907
components={{
853908
code: CodeBlock as any,
@@ -889,8 +944,11 @@ export function ChatNode({ id, data: initialData }: NodeProps) {
889944
}}
890945
remarkPlugins={[remarkGfm]}
891946
>
892-
{msg.content}
893-
</ReactMarkdown>
947+
{processedContent}
948+
</ReactMarkdown>
949+
</>
950+
);
951+
})()}
894952
{msg.role === "assistant" && msg.metrics && (
895953
<div className="text-xs text-gray-500 mt-1">
896954
{msg.metrics.totalTokens && `${msg.metrics.totalTokens} tokens`}

‎client/src/components/SettingsDialog.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
8080

8181
<div className="flex justify-center">
8282
<img src={logo} alt="Curiso.ai" title="Curiso.ai" className="w-12 h-12" /></div>
83-
<div className="flex justify-center"><p className="text-sm text-muted-foreground justify-center mb-2">Version v1.1.8 by <a
83+
<div className="flex justify-center"><p className="text-sm text-muted-foreground justify-center mb-2">Version v1.1.9 by <a
8484
href="https://github.com/metaspartan/curiso"
8585
onClick={(e) => {
8686
e.preventDefault();
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { cn } from "@/lib/utils";
2+
import { useState } from "react";
3+
import { ChevronRight } from "lucide-react";
4+
5+
export function ThinkingBlock({ children }: { children: React.ReactNode }) {
6+
const [isExpanded, setIsExpanded] = useState(false);
7+
8+
return (<>
9+
<div
10+
className="text-muted-foreground text-xs flex items-center gap-2 cursor-pointer select-none"
11+
onClick={() => setIsExpanded(!isExpanded)}
12+
>
13+
<ChevronRight className={cn(
14+
"h-3 w-3 transition-transform",
15+
isExpanded && "transform rotate-90"
16+
)} />
17+
<span>Thoughts</span>
18+
</div>
19+
<div className={cn(
20+
"border-muted border-t-2 border-b-2 pt-2 pb-2 mt-2 mb-2",
21+
"text-[hsl(var(--thinking-content))] text-xs",
22+
"thinking-content",
23+
!isExpanded && "max-h-[50px] overflow-hidden relative",
24+
)}>
25+
{children}
26+
{!isExpanded && (
27+
<div className="absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-background to-transparent" />
28+
)}
29+
</div>
30+
</>);
31+
}

‎client/src/index.css

+11
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
--secondary-foreground: 0 0% 98%;
8787
--muted: 240 3.7% 15.9%;
8888
--muted-foreground: 240 5% 64.9%;
89+
--thinking-content: 240 5% 44.9%;
8990
--accent: 240 3.7% 15.9%;
9091
--accent-foreground: 0 0% 98%;
9192
--destructive: 0 62.8% 30.6%;
@@ -112,6 +113,7 @@
112113
--secondary-foreground: 0 0% 98%;
113114
--muted: 240 3.7% 15.9%;
114115
--muted-foreground: 240 5% 64.9%;
116+
--thinking-content: 240 5% 44.9%;
115117
--accent: 240 3.7% 15.9%;
116118
--accent-foreground: 0 0% 98%;
117119
--destructive: 0 62.8% 30.6%;
@@ -145,4 +147,13 @@
145147
--chart-color-s: 65%;
146148
--chart-color-l: 55%;
147149
color: hsl(var(--chart-color-h) var(--chart-color-s) var(--chart-color-l));
150+
}
151+
152+
.thinking-content {
153+
color: hsl(var(--thinking-content));
154+
font-style: italic;
155+
/* padding: 0.5rem;
156+
margin: 0.5rem 0; */
157+
border-top: 2px solid hsl(var(--muted));
158+
border-bottom: 2px solid hsl(var(--muted));
148159
}

‎client/src/lib/constants.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,4 @@ export const DEFAULT_AI_SETTINGS = {
7979
description: "Custom endpoint URL",
8080
icon: logo
8181
}
82-
] as const;
82+
] as const;

‎client/src/lib/localmodels.ts

-90
Original file line numberDiff line numberDiff line change
@@ -21,96 +21,6 @@ type ModelResponse = {
2121
} | ExoModelResponse[];
2222

2323
export const defaultLocalModels: CustomModel[] = [
24-
// {
25-
// id: 'llama3.2:1b',
26-
// name: 'llama3.2:1b',
27-
// provider: 'openai',
28-
// description: 'Llama 3.2 1B model running locally via Ollama',
29-
// endpoint: 'http://localhost:11434/v1',
30-
// requiresAuth: false,
31-
// maxTokens: 8192,
32-
// thumbnailUrl: ollamaLogo
33-
// },
34-
// {
35-
// id: 'llama3.2',
36-
// name: 'llama3.2:3b',
37-
// provider: 'openai',
38-
// description: 'Llama 3.2 3B model running locally via Ollama',
39-
// endpoint: 'http://localhost:11434/v1',
40-
// requiresAuth: false,
41-
// maxTokens: 8192,
42-
// thumbnailUrl: ollamaLogo
43-
// },
44-
// {
45-
// id: 'llama3.3',
46-
// name: 'llama3.3',
47-
// provider: 'openai',
48-
// description: 'Llama 3.3 70B model running locally via Ollama',
49-
// endpoint: 'http://localhost:11434/v1',
50-
// requiresAuth: false,
51-
// maxTokens: 8192,
52-
// thumbnailUrl: ollamaLogo
53-
// },
54-
// {
55-
// id: 'llama3.2-vision',
56-
// name: 'llama3.2-vision:11b',
57-
// provider: 'openai',
58-
// description: 'Llama 3.2 Vision 11B model running locally via Ollama',
59-
// endpoint: 'http://localhost:11434/v1',
60-
// requiresAuth: false,
61-
// maxTokens: 8192,
62-
// thumbnailUrl: ollamaLogo
63-
// },
64-
// {
65-
// id: 'qwen2.5:1.5b',
66-
// name: 'qwen2.5:1.5b',
67-
// provider: 'openai',
68-
// description: 'Qwen 2.5 1.5B model running locally via Ollama',
69-
// endpoint: 'http://localhost:11434/v1',
70-
// requiresAuth: false,
71-
// maxTokens: 8192,
72-
// thumbnailUrl: ollamaLogo
73-
// },
74-
// {
75-
// id: 'qwen2.5:3b',
76-
// name: 'qwen2.5:3b',
77-
// provider: 'openai',
78-
// description: 'Qwen 2.5 3B model running locally via Ollama',
79-
// endpoint: 'http://localhost:11434/v1',
80-
// requiresAuth: false,
81-
// maxTokens: 8192,
82-
// thumbnailUrl: ollamaLogo
83-
// },
84-
// {
85-
// id: 'qwen2.5:7b',
86-
// name: 'qwen2.5:7b',
87-
// provider: 'openai',
88-
// description: 'Qwen 2.5 7B model running locally via Ollama',
89-
// endpoint: 'http://localhost:11434/v1',
90-
// requiresAuth: false,
91-
// maxTokens: 8192,
92-
// thumbnailUrl: ollamaLogo
93-
// },
94-
// {
95-
// id: 'phi3.5',
96-
// name: 'phi3.5',
97-
// provider: 'openai',
98-
// description: 'Phi 3.5 3.8B model running locally via Ollama',
99-
// endpoint: 'http://localhost:11434/v1',
100-
// requiresAuth: false,
101-
// maxTokens: 8192,
102-
// thumbnailUrl: ollamaLogo
103-
// },
104-
// {
105-
// id: 'phi4',
106-
// name: 'phi4',
107-
// provider: 'openai',
108-
// description: 'Phi 4 14B model running locally via Ollama',
109-
// endpoint: 'http://localhost:11434/v1',
110-
// requiresAuth: false,
111-
// maxTokens: 8192,
112-
// thumbnailUrl: ollamaLogo
113-
// }
11424
]
11525

11626
export class ModelService {

‎client/src/lib/remotemodels.ts

+157
Large diffs are not rendered by default.

‎client/src/lib/types.ts

+1-149
Large diffs are not rendered by default.

‎client/src/lib/utils.ts

+45
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,51 @@ export function sanitizeChatMessages(messages: any[]) {
1717
return messages.map(sanitizeChatMessage);
1818
}
1919

20+
export function processThinkingContent(content: string) {
21+
const blocks = [];
22+
let currentIndex = 0;
23+
// @ts-ignore
24+
const regex = /<think>(.*?)<\/think>/gs;
25+
let match;
26+
let processedContent = content;
27+
28+
while ((match = regex.exec(content)) !== null) {
29+
// Add any content before the thinking block
30+
if (match.index > currentIndex) {
31+
blocks.push({
32+
type: 'normal',
33+
content: content.slice(currentIndex, match.index),
34+
key: `normal-${currentIndex}`
35+
});
36+
}
37+
38+
// Add the thinking block
39+
blocks.push({
40+
type: 'thinking',
41+
content: match[1].trim(),
42+
key: `think-${match.index}`
43+
});
44+
45+
currentIndex = match.index + match[0].length;
46+
47+
// Remove the thinking block from the original content
48+
processedContent = processedContent.replace(match[0], '');
49+
}
50+
51+
if (currentIndex < content.length) {
52+
blocks.push({
53+
type: 'normal',
54+
content: processedContent.slice(currentIndex),
55+
key: `normal-${currentIndex}`
56+
});
57+
}
58+
59+
return {
60+
blocks: blocks.filter(block => block.content),
61+
processedContent: processedContent.trim()
62+
};
63+
}
64+
2065
export function isEqual(a: any, b: any): boolean {
2166
if (a === b) return true;
2267

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "curiso.ai",
3-
"version": "1.1.8",
3+
"version": "1.1.9",
44
"author": "Carsen Klock",
55
"type": "module",
66
"license": "MIT",

‎src-tauri/Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src-tauri/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "curiso-ai"
3-
version = "1.1.8"
3+
version = "1.1.9"
44
description = "Curiso AI Desktop"
55
authors = ["Carsen Klock"]
66
license = "MIT"

‎src-tauri/tauri.conf.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"identifier": "com.curiso.ai",
33
"productName": "Curiso AI",
4-
"version": "1.1.8",
4+
"version": "1.1.9",
55
"build": {
66
"beforeBuildCommand": "bun run build",
77
"beforeDevCommand": "bun run dev",

0 commit comments

Comments
 (0)
Please sign in to comment.