moelove commited on
Commit
320da1c
Β·
2 Parent(s): f044ea5 31abbc8

Merge xsai integration with MCP features

Browse files

- Combined xsai SDK integration with existing MCP functionality
- Resolved merge conflicts in server/index.js and package.json
- Both xsai AI model connections and MCP server management now work together
- Updated dependencies to include both xsai packages and MCP SDK
- Regenerated package-lock.json to reflect all dependencies

This merge brings together:
- xsai-powered AI streaming and summarization
- MCP server discovery and tool execution
- Enhanced reasoning extraction capabilities
- Unified server architecture supporting both features

CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  ## [0.1.1] - 2025-03-23
8
 
9
  ### Added
 
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
 
7
+ ## [0.2.0] - 2025-07-24
8
+
9
+ ### Added
10
+ - xsai integration for LLM provider connections
11
+ - Automatic reasoning extraction using `@xsai/utils-reasoning`
12
+ - Test script for xsai integration (`test-xsai.js`)
13
+ - Enhanced documentation for xsai usage
14
+
15
+ ### Changed
16
+ - **BREAKING**: Migrated from `node-fetch` to xsai SDK for all AI model connections
17
+ - Chat endpoint now uses `@xsai/stream-text` for streaming responses
18
+ - Summarization endpoint now uses `@xsai/generate-text` for text generation
19
+ - Improved error handling and streaming reliability
20
+ - Enhanced reasoning process extraction and display
21
+
22
+ ### Removed
23
+ - `node-fetch` dependency (replaced by xsai packages)
24
+
25
+ ### Technical Details
26
+ - Server now uses xsai's `streamText` and `generateText` functions
27
+ - Automatic extraction of thinking processes from model responses
28
+ - Better streaming performance and error handling
29
+ - Runtime-agnostic AI SDK support
30
+
31
  ## [0.1.1] - 2025-03-23
32
 
33
  ### Added
README.md CHANGED
@@ -37,6 +37,8 @@ A modern React-based chat application that provides a unique interface for inter
37
  - πŸ› οΈ **Modern Stack**: Built with React and Vite for optimal performance and development experience
38
  - πŸ§ͺ **Quality Assured**: Comprehensive unit tests ensure reliable functionality
39
  - πŸ”’ **Local Data Storage**: All data is stored locally for enhanced privacy and security
 
 
40
 
41
  ## Getting Started
42
 
@@ -93,3 +95,39 @@ A separate profile for conversation summarization:
93
  - **Model Name**: The model to use for summarization
94
 
95
  All settings are stored locally for privacy and security. You can manage multiple chat profiles and switch between them as needed.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  - πŸ› οΈ **Modern Stack**: Built with React and Vite for optimal performance and development experience
38
  - πŸ§ͺ **Quality Assured**: Comprehensive unit tests ensure reliable functionality
39
  - πŸ”’ **Local Data Storage**: All data is stored locally for enhanced privacy and security
40
+ - ⚑ **xsai Integration**: Powered by xsai (extra-small AI SDK) for efficient and lightweight AI model connections
41
+ - 🧩 **Reasoning Extraction**: Automatic extraction and visualization of AI reasoning processes using xsai utilities
42
 
43
  ## Getting Started
44
 
 
95
  - **Model Name**: The model to use for summarization
96
 
97
  All settings are stored locally for privacy and security. You can manage multiple chat profiles and switch between them as needed.
98
+
99
+ ## xsai Integration πŸ€–
100
+
101
+ This application now uses [xsai](https://github.com/moeru-ai/xsai) - an extra-small AI SDK for efficient LLM connections. The integration provides:
102
+
103
+ ### Key Benefits
104
+ - **Lightweight**: Minimal dependencies and small bundle size
105
+ - **Runtime Agnostic**: Works in Node.js, Deno, Bun, and browsers
106
+ - **Streaming Support**: Built-in streaming capabilities for real-time responses
107
+ - **Reasoning Extraction**: Automatic extraction of thinking processes from model responses
108
+
109
+ ### Technical Implementation
110
+ - **Chat Streaming**: Uses `@xsai/stream-text` for real-time message streaming
111
+ - **Summarization**: Uses `@xsai/generate-text` for conversation title generation
112
+ - **Reasoning Processing**: Uses `@xsai/utils-reasoning` to extract and display thinking processes
113
+
114
+ ### Testing xsai Integration
115
+
116
+ To test the xsai integration independently:
117
+
118
+ 1. Edit the `test-xsai.js` file with your API credentials
119
+ 2. Run the test script:
120
+
121
+ ```bash
122
+ node test-xsai.js
123
+ ```
124
+
125
+ This will test both text generation and streaming with reasoning extraction.
126
+
127
+ ### Migration from node-fetch
128
+
129
+ The application has been migrated from using `node-fetch` directly to using xsai's abstraction layer. This provides:
130
+ - Better error handling
131
+ - Consistent API across different model providers
132
+ - Built-in streaming utilities
133
+ - Simplified reasoning extraction
XSAI_INTEGRATION_SUMMARY.md ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # xsai Integration Summary
2
+
3
+ ## 🎯 Objective
4
+ Migrated the thinking-model-client from using `node-fetch` directly to using the [xsai](https://github.com/moeru-ai/xsai) library for LLM provider connections.
5
+
6
+ ## πŸ”„ Changes Made
7
+
8
+ ### 1. Dependencies Updated
9
+ - **Added**:
10
+ - `@xsai/stream-text@^0.3.3` - For streaming text responses
11
+ - `@xsai/generate-text@^0.3.3` - For text generation (summarization)
12
+ - `@xsai/utils-reasoning@^0.3.3` - For extracting reasoning from responses
13
+ - **Removed**:
14
+ - `node-fetch` - No longer needed
15
+
16
+ ### 2. Server Implementation (`server/index.js`)
17
+
18
+ #### Chat Endpoint (`/api/chat`)
19
+ - **Before**: Used `node-fetch` with manual streaming setup
20
+ - **After**: Uses `xsai.streamText()` with automatic reasoning extraction
21
+ - **Benefits**:
22
+ - Cleaner error handling
23
+ - Automatic reasoning/content separation
24
+ - Better streaming reliability
25
+ - Built-in support for different model formats
26
+
27
+ #### Summarization Endpoint (`/api/summarize`)
28
+ - **Before**: Used `node-fetch` with manual JSON parsing
29
+ - **After**: Uses `xsai.generateText()` for direct text generation
30
+ - **Benefits**:
31
+ - Simplified API calls
32
+ - Better error handling
33
+ - Consistent interface across providers
34
+
35
+ ### 3. Key Features Enhanced
36
+
37
+ #### Reasoning Extraction
38
+ - Automatically extracts `<think>...</think>` tags from AI responses
39
+ - Separates reasoning process from final answer
40
+ - Streams reasoning first, then content for better UX
41
+
42
+ #### Streaming Improvements
43
+ - More reliable streaming with better error handling
44
+ - Consistent format across different model providers
45
+ - Automatic handling of different response formats
46
+
47
+ ### 4. Testing & Documentation
48
+
49
+ #### Test Script (`test-xsai.js`)
50
+ - Created comprehensive test script to verify xsai integration
51
+ - Tests both `generateText` and `streamText` with reasoning extraction
52
+ - Provides easy way to validate setup with different API providers
53
+
54
+ #### Documentation Updates
55
+ - Updated README.md with xsai integration details
56
+ - Added technical implementation details
57
+ - Enhanced feature descriptions
58
+ - Updated CHANGELOG.md with breaking changes
59
+ - Version bumped to 0.2.0 (breaking change)
60
+
61
+ ## πŸš€ Benefits of xsai Integration
62
+
63
+ ### Performance
64
+ - **Lightweight**: Smaller bundle size compared to multiple HTTP client dependencies
65
+ - **Efficient**: Optimized for AI model interactions
66
+ - **Runtime Agnostic**: Works in Node.js, Deno, Bun, and browsers
67
+
68
+ ### Developer Experience
69
+ - **Simplified API**: Consistent interface across different model providers
70
+ - **Better Error Handling**: Built-in error handling and retry logic
71
+ - **Type Safety**: Better TypeScript support for AI interactions
72
+
73
+ ### Features
74
+ - **Automatic Reasoning Extraction**: Built-in support for thinking processes
75
+ - **Streaming Utilities**: Advanced streaming capabilities
76
+ - **Multiple Providers**: Easily switch between different AI providers
77
+
78
+ ## πŸ§ͺ Testing the Integration
79
+
80
+ 1. **Start the server**:
81
+ ```bash
82
+ npm run server
83
+ ```
84
+
85
+ 2. **Test with the frontend**:
86
+ ```bash
87
+ npm start
88
+ ```
89
+
90
+ 3. **Run standalone tests**:
91
+ ```bash
92
+ node test-xsai.js
93
+ ```
94
+ (After updating API credentials in the test file)
95
+
96
+ ## πŸ”§ Configuration
97
+
98
+ The application maintains the same configuration interface:
99
+ - API endpoints are automatically converted to xsai's baseURL format
100
+ - All existing profiles and settings continue to work
101
+ - No changes required to existing user configurations
102
+
103
+ ## πŸ“‹ Migration Notes
104
+
105
+ This is a **breaking change** internally but maintains API compatibility:
106
+ - Server endpoints (`/api/chat`, `/api/summarize`) maintain same interface
107
+ - Frontend code requires no changes
108
+ - User configurations remain compatible
109
+ - Docker deployments work without changes
110
+
111
+ ## πŸŽ‰ Result
112
+
113
+ The thinking-model-client now uses xsai for all LLM interactions, providing:
114
+ - More reliable streaming
115
+ - Better reasoning extraction
116
+ - Cleaner codebase
117
+ - Enhanced error handling
118
+ - Future-proof architecture for AI model connections
119
+
120
+ The migration is complete and fully functional!
package-lock.json CHANGED
@@ -1,18 +1,20 @@
1
  {
2
  "name": "thinking-model-client",
3
- "version": "0.1.1",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "thinking-model-client",
9
- "version": "0.1.1",
10
  "dependencies": {
11
  "@modelcontextprotocol/sdk": "^1.8.0",
 
 
 
12
  "cors": "^2.8.5",
13
  "eventsource": "^3.0.6",
14
  "express": "^4.18.2",
15
- "node-fetch": "^3.3.2",
16
  "react": "^18.2.0",
17
  "react-dom": "^18.2.0",
18
  "react-markdown": "^10.0.0",
@@ -909,17 +911,20 @@
909
  }
910
  },
911
  "node_modules/@modelcontextprotocol/sdk": {
912
- "version": "1.8.0",
913
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz",
914
- "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==",
 
915
  "dependencies": {
 
916
  "content-type": "^1.0.5",
917
  "cors": "^2.8.5",
918
- "cross-spawn": "^7.0.3",
919
  "eventsource": "^3.0.2",
 
920
  "express": "^5.0.1",
921
  "express-rate-limit": "^7.5.0",
922
- "pkce-challenge": "^4.1.0",
923
  "raw-body": "^3.0.0",
924
  "zod": "^3.23.8",
925
  "zod-to-json-schema": "^3.24.1"
@@ -932,6 +937,7 @@
932
  "version": "2.0.0",
933
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
934
  "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
 
935
  "dependencies": {
936
  "mime-types": "^3.0.0",
937
  "negotiator": "^1.0.0"
@@ -944,6 +950,7 @@
944
  "version": "2.2.0",
945
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
946
  "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
 
947
  "dependencies": {
948
  "bytes": "^3.1.2",
949
  "content-type": "^1.0.5",
@@ -963,6 +970,7 @@
963
  "version": "1.0.0",
964
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
965
  "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
 
966
  "dependencies": {
967
  "safe-buffer": "5.2.1"
968
  },
@@ -974,6 +982,7 @@
974
  "version": "1.2.2",
975
  "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
976
  "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
 
977
  "engines": {
978
  "node": ">=6.6.0"
979
  }
@@ -982,6 +991,7 @@
982
  "version": "5.1.0",
983
  "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
984
  "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
 
985
  "dependencies": {
986
  "accepts": "^2.0.0",
987
  "body-parser": "^2.2.0",
@@ -1023,6 +1033,7 @@
1023
  "version": "2.1.0",
1024
  "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
1025
  "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
 
1026
  "dependencies": {
1027
  "debug": "^4.4.0",
1028
  "encodeurl": "^2.0.0",
@@ -1039,6 +1050,7 @@
1039
  "version": "2.0.0",
1040
  "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
1041
  "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
 
1042
  "engines": {
1043
  "node": ">= 0.8"
1044
  }
@@ -1047,6 +1059,7 @@
1047
  "version": "0.6.3",
1048
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1049
  "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
 
1050
  "dependencies": {
1051
  "safer-buffer": ">= 2.1.2 < 3.0.0"
1052
  },
@@ -1058,6 +1071,7 @@
1058
  "version": "1.1.0",
1059
  "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
1060
  "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
 
1061
  "engines": {
1062
  "node": ">= 0.8"
1063
  }
@@ -1066,6 +1080,7 @@
1066
  "version": "2.0.0",
1067
  "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
1068
  "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
 
1069
  "engines": {
1070
  "node": ">=18"
1071
  },
@@ -1077,6 +1092,7 @@
1077
  "version": "1.54.0",
1078
  "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
1079
  "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
 
1080
  "engines": {
1081
  "node": ">= 0.6"
1082
  }
@@ -1085,6 +1101,7 @@
1085
  "version": "3.0.1",
1086
  "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
1087
  "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
 
1088
  "dependencies": {
1089
  "mime-db": "^1.54.0"
1090
  },
@@ -1096,6 +1113,7 @@
1096
  "version": "1.0.0",
1097
  "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
1098
  "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
 
1099
  "engines": {
1100
  "node": ">= 0.6"
1101
  }
@@ -1104,6 +1122,7 @@
1104
  "version": "6.14.0",
1105
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
1106
  "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
 
1107
  "dependencies": {
1108
  "side-channel": "^1.1.0"
1109
  },
@@ -1118,6 +1137,7 @@
1118
  "version": "3.0.0",
1119
  "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
1120
  "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
 
1121
  "dependencies": {
1122
  "bytes": "3.1.2",
1123
  "http-errors": "2.0.0",
@@ -1132,6 +1152,7 @@
1132
  "version": "1.2.0",
1133
  "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
1134
  "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
 
1135
  "dependencies": {
1136
  "debug": "^4.3.5",
1137
  "encodeurl": "^2.0.0",
@@ -1153,6 +1174,7 @@
1153
  "version": "2.2.0",
1154
  "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
1155
  "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
 
1156
  "dependencies": {
1157
  "encodeurl": "^2.0.0",
1158
  "escape-html": "^1.0.3",
@@ -1167,6 +1189,7 @@
1167
  "version": "2.0.1",
1168
  "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
1169
  "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
 
1170
  "dependencies": {
1171
  "content-type": "^1.0.5",
1172
  "media-typer": "^1.1.0",
@@ -1451,6 +1474,46 @@
1451
  "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
1452
  }
1453
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1454
  "node_modules/accepts": {
1455
  "version": "1.3.8",
1456
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -1476,6 +1539,22 @@
1476
  "node": ">=8"
1477
  }
1478
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1479
  "node_modules/ansi-colors": {
1480
  "version": "4.1.3",
1481
  "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -2462,14 +2541,6 @@
2462
  "node": ">=0.10"
2463
  }
2464
  },
2465
- "node_modules/data-uri-to-buffer": {
2466
- "version": "4.0.1",
2467
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
2468
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
2469
- "engines": {
2470
- "node": ">= 12"
2471
- }
2472
- },
2473
  "node_modules/date-fns": {
2474
  "version": "2.30.0",
2475
  "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -2824,9 +2895,10 @@
2824
  "dev": true
2825
  },
2826
  "node_modules/eventsource": {
2827
- "version": "3.0.6",
2828
- "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
2829
- "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
 
2830
  "dependencies": {
2831
  "eventsource-parser": "^3.0.1"
2832
  },
@@ -2835,11 +2907,12 @@
2835
  }
2836
  },
2837
  "node_modules/eventsource-parser": {
2838
- "version": "3.0.1",
2839
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
2840
- "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
 
2841
  "engines": {
2842
- "node": ">=18.0.0"
2843
  }
2844
  },
2845
  "node_modules/execa": {
@@ -2929,9 +3002,10 @@
2929
  }
2930
  },
2931
  "node_modules/express-rate-limit": {
2932
- "version": "7.5.0",
2933
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
2934
- "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
 
2935
  "engines": {
2936
  "node": ">= 16"
2937
  },
@@ -2939,7 +3013,7 @@
2939
  "url": "https://github.com/sponsors/express-rate-limit"
2940
  },
2941
  "peerDependencies": {
2942
- "express": "^4.11 || 5 || ^5.0.0-beta.1"
2943
  }
2944
  },
2945
  "node_modules/express/node_modules/debug": {
@@ -2989,6 +3063,12 @@
2989
  "node >=0.6.0"
2990
  ]
2991
  },
 
 
 
 
 
 
2992
  "node_modules/fast-glob": {
2993
  "version": "3.3.3",
2994
  "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -3017,6 +3097,12 @@
3017
  "node": ">= 6"
3018
  }
3019
  },
 
 
 
 
 
 
3020
  "node_modules/fastq": {
3021
  "version": "1.19.1",
3022
  "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
@@ -3035,28 +3121,6 @@
3035
  "pend": "~1.2.0"
3036
  }
3037
  },
3038
- "node_modules/fetch-blob": {
3039
- "version": "3.2.0",
3040
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
3041
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
3042
- "funding": [
3043
- {
3044
- "type": "github",
3045
- "url": "https://github.com/sponsors/jimmywarting"
3046
- },
3047
- {
3048
- "type": "paypal",
3049
- "url": "https://paypal.me/jimmywarting"
3050
- }
3051
- ],
3052
- "dependencies": {
3053
- "node-domexception": "^1.0.0",
3054
- "web-streams-polyfill": "^3.0.3"
3055
- },
3056
- "engines": {
3057
- "node": "^12.20 || >= 14.13"
3058
- }
3059
- },
3060
  "node_modules/figures": {
3061
  "version": "3.2.0",
3062
  "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -3174,17 +3238,6 @@
3174
  "node": ">= 6"
3175
  }
3176
  },
3177
- "node_modules/formdata-polyfill": {
3178
- "version": "4.0.10",
3179
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
3180
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
3181
- "dependencies": {
3182
- "fetch-blob": "^3.1.2"
3183
- },
3184
- "engines": {
3185
- "node": ">=12.20.0"
3186
- }
3187
- },
3188
  "node_modules/forwarded": {
3189
  "version": "0.2.0",
3190
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -3821,7 +3874,8 @@
3821
  "node_modules/is-promise": {
3822
  "version": "4.0.0",
3823
  "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
3824
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
 
3825
  },
3826
  "node_modules/is-stream": {
3827
  "version": "2.0.1",
@@ -3930,6 +3984,12 @@
3930
  "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
3931
  "dev": true
3932
  },
 
 
 
 
 
 
3933
  "node_modules/json-stringify-safe": {
3934
  "version": "5.0.1",
3935
  "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -5127,41 +5187,6 @@
5127
  "node": ">= 0.6"
5128
  }
5129
  },
5130
- "node_modules/node-domexception": {
5131
- "version": "1.0.0",
5132
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
5133
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
5134
- "funding": [
5135
- {
5136
- "type": "github",
5137
- "url": "https://github.com/sponsors/jimmywarting"
5138
- },
5139
- {
5140
- "type": "github",
5141
- "url": "https://paypal.me/jimmywarting"
5142
- }
5143
- ],
5144
- "engines": {
5145
- "node": ">=10.5.0"
5146
- }
5147
- },
5148
- "node_modules/node-fetch": {
5149
- "version": "3.3.2",
5150
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
5151
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
5152
- "dependencies": {
5153
- "data-uri-to-buffer": "^4.0.0",
5154
- "fetch-blob": "^3.1.4",
5155
- "formdata-polyfill": "^4.0.10"
5156
- },
5157
- "engines": {
5158
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
5159
- },
5160
- "funding": {
5161
- "type": "opencollective",
5162
- "url": "https://opencollective.com/node-fetch"
5163
- }
5164
- },
5165
  "node_modules/node-releases": {
5166
  "version": "2.0.19",
5167
  "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -5426,9 +5451,10 @@
5426
  }
5427
  },
5428
  "node_modules/pkce-challenge": {
5429
- "version": "4.1.0",
5430
- "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz",
5431
- "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==",
 
5432
  "engines": {
5433
  "node": ">=16.20.0"
5434
  }
@@ -5700,6 +5726,15 @@
5700
  "once": "^1.3.1"
5701
  }
5702
  },
 
 
 
 
 
 
 
 
 
5703
  "node_modules/qs": {
5704
  "version": "6.13.0",
5705
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -5977,6 +6012,7 @@
5977
  "version": "2.2.0",
5978
  "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
5979
  "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
 
5980
  "dependencies": {
5981
  "debug": "^4.4.0",
5982
  "depd": "^2.0.0",
@@ -5992,6 +6028,7 @@
5992
  "version": "8.2.0",
5993
  "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
5994
  "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
 
5995
  "engines": {
5996
  "node": ">=16"
5997
  }
@@ -6917,6 +6954,15 @@
6917
  "browserslist": ">= 4.21.0"
6918
  }
6919
  },
 
 
 
 
 
 
 
 
 
6920
  "node_modules/url-join": {
6921
  "version": "4.0.1",
6922
  "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
@@ -7068,14 +7114,6 @@
7068
  "node": ">=12.0.0"
7069
  }
7070
  },
7071
- "node_modules/web-streams-polyfill": {
7072
- "version": "3.3.3",
7073
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
7074
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
7075
- "engines": {
7076
- "node": ">= 8"
7077
- }
7078
- },
7079
  "node_modules/whatwg-encoding": {
7080
  "version": "2.0.0",
7081
  "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
@@ -7219,17 +7257,19 @@
7219
  }
7220
  },
7221
  "node_modules/zod": {
7222
- "version": "3.24.2",
7223
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
7224
- "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
 
7225
  "funding": {
7226
  "url": "https://github.com/sponsors/colinhacks"
7227
  }
7228
  },
7229
  "node_modules/zod-to-json-schema": {
7230
- "version": "3.24.5",
7231
- "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
7232
- "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
 
7233
  "peerDependencies": {
7234
  "zod": "^3.24.1"
7235
  }
 
1
  {
2
  "name": "thinking-model-client",
3
+ "version": "0.2.0",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "thinking-model-client",
9
+ "version": "0.2.0",
10
  "dependencies": {
11
  "@modelcontextprotocol/sdk": "^1.8.0",
12
+ "@xsai/generate-text": "^0.3.3",
13
+ "@xsai/stream-text": "^0.3.3",
14
+ "@xsai/utils-reasoning": "^0.3.3",
15
  "cors": "^2.8.5",
16
  "eventsource": "^3.0.6",
17
  "express": "^4.18.2",
 
18
  "react": "^18.2.0",
19
  "react-dom": "^18.2.0",
20
  "react-markdown": "^10.0.0",
 
911
  }
912
  },
913
  "node_modules/@modelcontextprotocol/sdk": {
914
+ "version": "1.17.0",
915
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.0.tgz",
916
+ "integrity": "sha512-qFfbWFA7r1Sd8D697L7GkTd36yqDuTkvz0KfOGkgXR8EUhQn3/EDNIR/qUdQNMT8IjmasBvHWuXeisxtXTQT2g==",
917
+ "license": "MIT",
918
  "dependencies": {
919
+ "ajv": "^6.12.6",
920
  "content-type": "^1.0.5",
921
  "cors": "^2.8.5",
922
+ "cross-spawn": "^7.0.5",
923
  "eventsource": "^3.0.2",
924
+ "eventsource-parser": "^3.0.0",
925
  "express": "^5.0.1",
926
  "express-rate-limit": "^7.5.0",
927
+ "pkce-challenge": "^5.0.0",
928
  "raw-body": "^3.0.0",
929
  "zod": "^3.23.8",
930
  "zod-to-json-schema": "^3.24.1"
 
937
  "version": "2.0.0",
938
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
939
  "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
940
+ "license": "MIT",
941
  "dependencies": {
942
  "mime-types": "^3.0.0",
943
  "negotiator": "^1.0.0"
 
950
  "version": "2.2.0",
951
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
952
  "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
953
+ "license": "MIT",
954
  "dependencies": {
955
  "bytes": "^3.1.2",
956
  "content-type": "^1.0.5",
 
970
  "version": "1.0.0",
971
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
972
  "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
973
+ "license": "MIT",
974
  "dependencies": {
975
  "safe-buffer": "5.2.1"
976
  },
 
982
  "version": "1.2.2",
983
  "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
984
  "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
985
+ "license": "MIT",
986
  "engines": {
987
  "node": ">=6.6.0"
988
  }
 
991
  "version": "5.1.0",
992
  "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
993
  "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
994
+ "license": "MIT",
995
  "dependencies": {
996
  "accepts": "^2.0.0",
997
  "body-parser": "^2.2.0",
 
1033
  "version": "2.1.0",
1034
  "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
1035
  "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
1036
+ "license": "MIT",
1037
  "dependencies": {
1038
  "debug": "^4.4.0",
1039
  "encodeurl": "^2.0.0",
 
1050
  "version": "2.0.0",
1051
  "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
1052
  "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
1053
+ "license": "MIT",
1054
  "engines": {
1055
  "node": ">= 0.8"
1056
  }
 
1059
  "version": "0.6.3",
1060
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1061
  "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
1062
+ "license": "MIT",
1063
  "dependencies": {
1064
  "safer-buffer": ">= 2.1.2 < 3.0.0"
1065
  },
 
1071
  "version": "1.1.0",
1072
  "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
1073
  "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
1074
+ "license": "MIT",
1075
  "engines": {
1076
  "node": ">= 0.8"
1077
  }
 
1080
  "version": "2.0.0",
1081
  "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
1082
  "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
1083
+ "license": "MIT",
1084
  "engines": {
1085
  "node": ">=18"
1086
  },
 
1092
  "version": "1.54.0",
1093
  "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
1094
  "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
1095
+ "license": "MIT",
1096
  "engines": {
1097
  "node": ">= 0.6"
1098
  }
 
1101
  "version": "3.0.1",
1102
  "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
1103
  "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
1104
+ "license": "MIT",
1105
  "dependencies": {
1106
  "mime-db": "^1.54.0"
1107
  },
 
1113
  "version": "1.0.0",
1114
  "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
1115
  "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
1116
+ "license": "MIT",
1117
  "engines": {
1118
  "node": ">= 0.6"
1119
  }
 
1122
  "version": "6.14.0",
1123
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
1124
  "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
1125
+ "license": "BSD-3-Clause",
1126
  "dependencies": {
1127
  "side-channel": "^1.1.0"
1128
  },
 
1137
  "version": "3.0.0",
1138
  "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
1139
  "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
1140
+ "license": "MIT",
1141
  "dependencies": {
1142
  "bytes": "3.1.2",
1143
  "http-errors": "2.0.0",
 
1152
  "version": "1.2.0",
1153
  "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
1154
  "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
1155
+ "license": "MIT",
1156
  "dependencies": {
1157
  "debug": "^4.3.5",
1158
  "encodeurl": "^2.0.0",
 
1174
  "version": "2.2.0",
1175
  "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
1176
  "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
1177
+ "license": "MIT",
1178
  "dependencies": {
1179
  "encodeurl": "^2.0.0",
1180
  "escape-html": "^1.0.3",
 
1189
  "version": "2.0.1",
1190
  "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
1191
  "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
1192
+ "license": "MIT",
1193
  "dependencies": {
1194
  "content-type": "^1.0.5",
1195
  "media-typer": "^1.1.0",
 
1474
  "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
1475
  }
1476
  },
1477
+ "node_modules/@xsai/generate-text": {
1478
+ "version": "0.3.3",
1479
+ "resolved": "https://registry.npmjs.org/@xsai/generate-text/-/generate-text-0.3.3.tgz",
1480
+ "integrity": "sha512-lVaUzbIgGOdsbKUm5p1ftSYteq3tMCismodY7tK+MEOlaEErmSeS+SRLZmz6X2Jn23ZuYsrj1gbGqdTciBjgHg==",
1481
+ "license": "MIT",
1482
+ "dependencies": {
1483
+ "@xsai/shared": "~0.3.3",
1484
+ "@xsai/shared-chat": "~0.3.3"
1485
+ }
1486
+ },
1487
+ "node_modules/@xsai/shared": {
1488
+ "version": "0.3.3",
1489
+ "resolved": "https://registry.npmjs.org/@xsai/shared/-/shared-0.3.3.tgz",
1490
+ "integrity": "sha512-1xul8h7He5cM+H/gGx8pdE/LLqujO3xSHM2+V4XVEjI05VxQq+s5lk42/7NUUbjcvcFxi5Ow9IL4pezHgxq9aQ==",
1491
+ "license": "MIT"
1492
+ },
1493
+ "node_modules/@xsai/shared-chat": {
1494
+ "version": "0.3.3",
1495
+ "resolved": "https://registry.npmjs.org/@xsai/shared-chat/-/shared-chat-0.3.3.tgz",
1496
+ "integrity": "sha512-zCfAlXhNfQ3+ErhP8BnSIdsELAENHubfscLdImls+9zNHfY1AzIaBuRIf5dWJYmC4/NsOop1QHYjfM+7Wg4gjw==",
1497
+ "license": "MIT",
1498
+ "dependencies": {
1499
+ "@xsai/shared": "~0.3.3"
1500
+ }
1501
+ },
1502
+ "node_modules/@xsai/stream-text": {
1503
+ "version": "0.3.3",
1504
+ "resolved": "https://registry.npmjs.org/@xsai/stream-text/-/stream-text-0.3.3.tgz",
1505
+ "integrity": "sha512-Y/f4EkvWIF6nJ/07RJpneAugMV6pHiDi1c0X2pn6m6NhGodpj1HZKzvYTFryW/ZYc5NCG+B7AXUQyCy8PUSOeg==",
1506
+ "license": "MIT",
1507
+ "dependencies": {
1508
+ "@xsai/shared-chat": "~0.3.3"
1509
+ }
1510
+ },
1511
+ "node_modules/@xsai/utils-reasoning": {
1512
+ "version": "0.3.3",
1513
+ "resolved": "https://registry.npmjs.org/@xsai/utils-reasoning/-/utils-reasoning-0.3.3.tgz",
1514
+ "integrity": "sha512-4YiikRMij9ns9I2qEZeyHrG3SSfghfxMJU4DpvNdmZznDT3OujUMHrgD3BuvWXTyuGuyAyV8ZiZ0TR+DXl/Rkg==",
1515
+ "license": "MIT"
1516
+ },
1517
  "node_modules/accepts": {
1518
  "version": "1.3.8",
1519
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
 
1539
  "node": ">=8"
1540
  }
1541
  },
1542
+ "node_modules/ajv": {
1543
+ "version": "6.12.6",
1544
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
1545
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
1546
+ "license": "MIT",
1547
+ "dependencies": {
1548
+ "fast-deep-equal": "^3.1.1",
1549
+ "fast-json-stable-stringify": "^2.0.0",
1550
+ "json-schema-traverse": "^0.4.1",
1551
+ "uri-js": "^4.2.2"
1552
+ },
1553
+ "funding": {
1554
+ "type": "github",
1555
+ "url": "https://github.com/sponsors/epoberezkin"
1556
+ }
1557
+ },
1558
  "node_modules/ansi-colors": {
1559
  "version": "4.1.3",
1560
  "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
 
2541
  "node": ">=0.10"
2542
  }
2543
  },
 
 
 
 
 
 
 
 
2544
  "node_modules/date-fns": {
2545
  "version": "2.30.0",
2546
  "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
 
2895
  "dev": true
2896
  },
2897
  "node_modules/eventsource": {
2898
+ "version": "3.0.7",
2899
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
2900
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
2901
+ "license": "MIT",
2902
  "dependencies": {
2903
  "eventsource-parser": "^3.0.1"
2904
  },
 
2907
  }
2908
  },
2909
  "node_modules/eventsource-parser": {
2910
+ "version": "3.0.3",
2911
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
2912
+ "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==",
2913
+ "license": "MIT",
2914
  "engines": {
2915
+ "node": ">=20.0.0"
2916
  }
2917
  },
2918
  "node_modules/execa": {
 
3002
  }
3003
  },
3004
  "node_modules/express-rate-limit": {
3005
+ "version": "7.5.1",
3006
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
3007
+ "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
3008
+ "license": "MIT",
3009
  "engines": {
3010
  "node": ">= 16"
3011
  },
 
3013
  "url": "https://github.com/sponsors/express-rate-limit"
3014
  },
3015
  "peerDependencies": {
3016
+ "express": ">= 4.11"
3017
  }
3018
  },
3019
  "node_modules/express/node_modules/debug": {
 
3063
  "node >=0.6.0"
3064
  ]
3065
  },
3066
+ "node_modules/fast-deep-equal": {
3067
+ "version": "3.1.3",
3068
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
3069
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
3070
+ "license": "MIT"
3071
+ },
3072
  "node_modules/fast-glob": {
3073
  "version": "3.3.3",
3074
  "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
 
3097
  "node": ">= 6"
3098
  }
3099
  },
3100
+ "node_modules/fast-json-stable-stringify": {
3101
+ "version": "2.1.0",
3102
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
3103
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
3104
+ "license": "MIT"
3105
+ },
3106
  "node_modules/fastq": {
3107
  "version": "1.19.1",
3108
  "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
 
3121
  "pend": "~1.2.0"
3122
  }
3123
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3124
  "node_modules/figures": {
3125
  "version": "3.2.0",
3126
  "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
 
3238
  "node": ">= 6"
3239
  }
3240
  },
 
 
 
 
 
 
 
 
 
 
 
3241
  "node_modules/forwarded": {
3242
  "version": "0.2.0",
3243
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
 
3874
  "node_modules/is-promise": {
3875
  "version": "4.0.0",
3876
  "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
3877
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
3878
+ "license": "MIT"
3879
  },
3880
  "node_modules/is-stream": {
3881
  "version": "2.0.1",
 
3984
  "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
3985
  "dev": true
3986
  },
3987
+ "node_modules/json-schema-traverse": {
3988
+ "version": "0.4.1",
3989
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
3990
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
3991
+ "license": "MIT"
3992
+ },
3993
  "node_modules/json-stringify-safe": {
3994
  "version": "5.0.1",
3995
  "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
 
5187
  "node": ">= 0.6"
5188
  }
5189
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5190
  "node_modules/node-releases": {
5191
  "version": "2.0.19",
5192
  "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
 
5451
  }
5452
  },
5453
  "node_modules/pkce-challenge": {
5454
+ "version": "5.0.0",
5455
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
5456
+ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
5457
+ "license": "MIT",
5458
  "engines": {
5459
  "node": ">=16.20.0"
5460
  }
 
5726
  "once": "^1.3.1"
5727
  }
5728
  },
5729
+ "node_modules/punycode": {
5730
+ "version": "2.3.1",
5731
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
5732
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
5733
+ "license": "MIT",
5734
+ "engines": {
5735
+ "node": ">=6"
5736
+ }
5737
+ },
5738
  "node_modules/qs": {
5739
  "version": "6.13.0",
5740
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
 
6012
  "version": "2.2.0",
6013
  "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
6014
  "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
6015
+ "license": "MIT",
6016
  "dependencies": {
6017
  "debug": "^4.4.0",
6018
  "depd": "^2.0.0",
 
6028
  "version": "8.2.0",
6029
  "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
6030
  "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
6031
+ "license": "MIT",
6032
  "engines": {
6033
  "node": ">=16"
6034
  }
 
6954
  "browserslist": ">= 4.21.0"
6955
  }
6956
  },
6957
+ "node_modules/uri-js": {
6958
+ "version": "4.4.1",
6959
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
6960
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
6961
+ "license": "BSD-2-Clause",
6962
+ "dependencies": {
6963
+ "punycode": "^2.1.0"
6964
+ }
6965
+ },
6966
  "node_modules/url-join": {
6967
  "version": "4.0.1",
6968
  "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
 
7114
  "node": ">=12.0.0"
7115
  }
7116
  },
 
 
 
 
 
 
 
 
7117
  "node_modules/whatwg-encoding": {
7118
  "version": "2.0.0",
7119
  "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
 
7257
  }
7258
  },
7259
  "node_modules/zod": {
7260
+ "version": "3.25.76",
7261
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
7262
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
7263
+ "license": "MIT",
7264
  "funding": {
7265
  "url": "https://github.com/sponsors/colinhacks"
7266
  }
7267
  },
7268
  "node_modules/zod-to-json-schema": {
7269
+ "version": "3.24.6",
7270
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
7271
+ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
7272
+ "license": "ISC",
7273
  "peerDependencies": {
7274
  "zod": "^3.24.1"
7275
  }
package.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "name": "thinking-model-client",
3
  "private": true,
4
- "version": "0.1.1",
5
  "type": "module",
6
  "scripts": {
7
  "dev": "vite",
@@ -19,10 +19,12 @@
19
  },
20
  "dependencies": {
21
  "@modelcontextprotocol/sdk": "^1.8.0",
 
 
 
22
  "cors": "^2.8.5",
23
  "eventsource": "^3.0.6",
24
  "express": "^4.18.2",
25
- "node-fetch": "^3.3.2",
26
  "react": "^18.2.0",
27
  "react-dom": "^18.2.0",
28
  "react-markdown": "^10.0.0",
 
1
  {
2
  "name": "thinking-model-client",
3
  "private": true,
4
+ "version": "0.2.0",
5
  "type": "module",
6
  "scripts": {
7
  "dev": "vite",
 
19
  },
20
  "dependencies": {
21
  "@modelcontextprotocol/sdk": "^1.8.0",
22
+ "@xsai/generate-text": "^0.3.3",
23
+ "@xsai/stream-text": "^0.3.3",
24
+ "@xsai/utils-reasoning": "^0.3.3",
25
  "cors": "^2.8.5",
26
  "eventsource": "^3.0.6",
27
  "express": "^4.18.2",
 
28
  "react": "^18.2.0",
29
  "react-dom": "^18.2.0",
30
  "react-markdown": "^10.0.0",
server/index.js CHANGED
@@ -1,8 +1,10 @@
1
  import express from 'express';
2
  import cors from 'cors';
3
- import fetch from 'node-fetch';
4
  import path from 'path'
5
  import { fileURLToPath } from 'url'
 
 
 
6
  import { callMcpServer, discoverMcpServerTools, executeMcpTool } from './mcp.js';
7
  import {
8
  getAvailableMcpServers,
@@ -31,44 +33,29 @@ app.post('/api/summarize', async (req, res) => {
31
  });
32
 
33
  try {
34
- let apiUrl;
35
  if (apiEndpoint.endsWith('#')) {
36
- apiUrl = apiEndpoint.slice(0, -1);
37
  } else if (apiEndpoint.endsWith('/')) {
38
- apiUrl = `${apiEndpoint}chat/completions`;
39
  } else {
40
- apiUrl = `${apiEndpoint}/v1/chat/completions`;
41
  }
42
- console.log('Calling API endpoint:', apiUrl);
43
-
44
- const response = await fetch(apiUrl, {
45
- method: 'POST',
46
- headers: {
47
- 'Content-Type': 'application/json',
48
- 'Authorization': `Bearer ${apiKey}`
49
- },
50
- body: JSON.stringify({
51
- model: model,
52
- messages: [{
53
- role: 'user',
54
- content: `Summarize this conversation in 3-5 words: ${content}`
55
- }],
56
- temperature: 0.2,
57
- max_tokens: 20
58
- })
59
  });
60
 
61
- if (!response.ok) {
62
- const errorData = await response.text();
63
- console.error('API error:', {
64
- status: response.status,
65
- error: errorData
66
- });
67
- throw new Error(`API error: ${response.status} - ${errorData}`);
68
- }
69
-
70
- const data = await response.json();
71
- const summary = data.choices[0].message.content.trim();
72
  res.json({ summary });
73
  } catch (error) {
74
  console.error('Error:', error);
@@ -86,40 +73,26 @@ app.post('/api/chat', async (req, res) => {
86
  });
87
 
88
  try {
89
- let apiUrl;
90
  if (apiEndpoint.endsWith('#')) {
91
- apiUrl = apiEndpoint.slice(0, -1);
92
  } else if (apiEndpoint.endsWith('/')) {
93
- apiUrl = `${apiEndpoint}chat/completions`;
94
  } else {
95
- apiUrl = `${apiEndpoint}/v1/chat/completions`;
96
  }
97
- console.log('Calling API endpoint:', apiUrl);
98
-
99
- const response = await fetch(apiUrl, {
100
- method: 'POST',
101
- headers: {
102
- 'Content-Type': 'application/json',
103
- 'Authorization': `Bearer ${apiKey}`
104
- },
105
- body: JSON.stringify({
106
- model: model,
107
- messages: messages,
108
- stream: true
109
- })
110
  });
111
 
112
- console.log('API response status:', response.status);
113
- console.log('API response headers:', Object.fromEntries(response.headers.entries()));
114
-
115
- if (!response.ok) {
116
- const errorData = await response.text();
117
- console.error('API error:', {
118
- status: response.status,
119
- error: errorData
120
- });
121
- throw new Error(`API error: ${response.status} - ${errorData}`);
122
- }
123
 
124
  // Set headers for streaming
125
  res.setHeader('Content-Type', 'text/event-stream');
@@ -133,16 +106,71 @@ app.post('/api/chat', async (req, res) => {
133
  'Connection': 'keep-alive'
134
  });
135
 
136
- // Pipe the API response to the client
137
- response.body.pipe(res).on('error', (err) => {
138
- console.error('Stream error:', err);
139
- res.status(500).end();
140
- }).on('end', () => {
141
- console.log('Stream completed successfully');
 
142
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  } catch (error) {
144
  console.error('Error:', error);
145
- res.status(500).json({ error: error.message });
 
 
146
  }
147
  });
148
 
 
1
  import express from 'express';
2
  import cors from 'cors';
 
3
  import path from 'path'
4
  import { fileURLToPath } from 'url'
5
+ import { streamText } from '@xsai/stream-text';
6
+ import { generateText } from '@xsai/generate-text';
7
+ import { extractReasoningStream } from '@xsai/utils-reasoning';
8
  import { callMcpServer, discoverMcpServerTools, executeMcpTool } from './mcp.js';
9
  import {
10
  getAvailableMcpServers,
 
33
  });
34
 
35
  try {
36
+ let baseURL;
37
  if (apiEndpoint.endsWith('#')) {
38
+ baseURL = apiEndpoint.slice(0, -1);
39
  } else if (apiEndpoint.endsWith('/')) {
40
+ baseURL = `${apiEndpoint}v1`;
41
  } else {
42
+ baseURL = `${apiEndpoint}/v1`;
43
  }
44
+ console.log('Calling API baseURL:', baseURL);
45
+
46
+ const { text } = await generateText({
47
+ apiKey: apiKey,
48
+ baseURL: baseURL,
49
+ model: model,
50
+ messages: [{
51
+ role: 'user',
52
+ content: `Summarize this conversation in 3-5 words: ${content}`
53
+ }],
54
+ temperature: 0.2,
55
+ max_tokens: 20
 
 
 
 
 
56
  });
57
 
58
+ const summary = text.trim();
 
 
 
 
 
 
 
 
 
 
59
  res.json({ summary });
60
  } catch (error) {
61
  console.error('Error:', error);
 
73
  });
74
 
75
  try {
76
+ let baseURL;
77
  if (apiEndpoint.endsWith('#')) {
78
+ baseURL = apiEndpoint.slice(0, -1);
79
  } else if (apiEndpoint.endsWith('/')) {
80
+ baseURL = `${apiEndpoint}v1`;
81
  } else {
82
+ baseURL = `${apiEndpoint}/v1`;
83
  }
84
+ console.log('Calling API baseURL:', baseURL);
85
+
86
+ // Use xsai to stream text from the AI model
87
+ const { textStream } = await streamText({
88
+ apiKey: apiKey,
89
+ baseURL: baseURL,
90
+ model: model,
91
+ messages: messages
 
 
 
 
 
92
  });
93
 
94
+ // Extract reasoning and content streams
95
+ const { reasoningStream, textStream: contentStream } = extractReasoningStream(textStream);
 
 
 
 
 
 
 
 
 
96
 
97
  // Set headers for streaming
98
  res.setHeader('Content-Type', 'text/event-stream');
 
106
  'Connection': 'keep-alive'
107
  });
108
 
109
+ let reasoningText = '';
110
+ let contentText = '';
111
+ let isReasoningComplete = false;
112
+
113
+ // Handle client disconnect
114
+ req.on('close', () => {
115
+ console.log('Client disconnected');
116
  });
117
+
118
+ try {
119
+ // First, collect all reasoning
120
+ console.log('Collecting reasoning...');
121
+ for await (const chunk of reasoningStream) {
122
+ reasoningText += chunk;
123
+ }
124
+ isReasoningComplete = true;
125
+ console.log('Reasoning collection complete');
126
+
127
+ // If we have reasoning, send it first wrapped in think tags
128
+ if (reasoningText.trim()) {
129
+ const thinkingChunk = {
130
+ choices: [{
131
+ delta: {
132
+ content: `<think>${reasoningText}</think>`
133
+ }
134
+ }]
135
+ };
136
+ res.write(`data: ${JSON.stringify(thinkingChunk)}\n\n`);
137
+ }
138
+
139
+ // Then stream the content
140
+ console.log('Starting content stream...');
141
+ for await (const chunk of contentStream) {
142
+ if (res.destroyed) break;
143
+
144
+ const responseChunk = {
145
+ choices: [{
146
+ delta: {
147
+ content: chunk
148
+ }
149
+ }]
150
+ };
151
+
152
+ res.write(`data: ${JSON.stringify(responseChunk)}\n\n`);
153
+ contentText += chunk;
154
+ }
155
+
156
+ // Send completion marker
157
+ res.write('data: [DONE]\n\n');
158
+ res.end();
159
+ console.log('Stream completed successfully');
160
+
161
+ } catch (streamError) {
162
+ console.error('Streaming error:', streamError);
163
+ if (!res.destroyed) {
164
+ res.write(`data: ${JSON.stringify({ error: streamError.message })}\n\n`);
165
+ res.end();
166
+ }
167
+ }
168
+
169
  } catch (error) {
170
  console.error('Error:', error);
171
+ if (!res.destroyed) {
172
+ res.status(500).json({ error: error.message });
173
+ }
174
  }
175
  });
176
 
src/App.jsx CHANGED
@@ -143,47 +143,100 @@ function App() {
143
  };
144
 
145
  return (
146
- <div className="flex flex-col h-screen w-full overflow-hidden">
147
- <div className="flex justify-between items-center px-5 h-header border-b border-border bg-background">
148
- <div className="flex items-center gap-2">
 
149
  <button
150
- className="md:hidden bg-transparent border-0 text-base text-lightest-text cursor-pointer flex items-center justify-center z-10 w-8 h-8 hover:text-text"
151
  onClick={toggleMobileMenu}
152
  >
153
- ☰
 
 
154
  </button>
155
- <h1 className="text-xl font-semibold text-text">Thinking Model Client</h1>
 
 
 
 
 
 
 
 
 
 
 
 
156
  </div>
157
- <div className="flex items-center gap-2.5">
 
 
 
158
  <div className="relative">
159
  <button
160
- className="py-1.5 px-3 bg-background border border-border rounded text-sm text-text cursor-pointer flex items-center gap-1.5"
161
  onClick={toggleProfileDropdown}
162
  >
163
- {activeProfile.name} β–Ό
 
 
 
 
 
 
164
  </button>
 
165
  {showProfileDropdown && (
166
- <div className="absolute top-full right-0 w-[200px] bg-background border border-border rounded shadow-md z-10 mt-1.5">
167
- {profiles.map(profile => (
168
- <div
169
- key={profile.id}
170
- className={`p-3 cursor-pointer transition-colors duration-200 text-sm ${profile.id === activeProfileId ? 'bg-active font-medium' : 'hover:bg-hover'}`}
171
- onClick={() => handleProfileSelect(profile.id)}
172
- >
173
- {profile.name}
174
  </div>
175
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  </div>
177
  )}
178
  </div>
 
 
179
  <button
180
- className="py-1.5 px-3 bg-background border border-border rounded text-sm text-text cursor-pointer hover:bg-hover"
181
  onClick={toggleSettings}
182
  >
183
- Settings
 
 
 
 
184
  </button>
185
  </div>
186
- </div>
187
 
188
  <div className="flex flex-1 overflow-hidden">
189
  <div className={`sidebar ${sidebarCollapsed ? 'collapsed' : ''} ${mobileMenuOpen ? 'mobile-open' : ''}`}>
 
143
  };
144
 
145
  return (
146
+ <div className="flex flex-col h-screen w-full overflow-hidden bg-background-secondary">
147
+ {/* Modern Enterprise Header */}
148
+ <header className="flex justify-between items-center px-6 h-header bg-background-elevated border-b border-border shadow-sm">
149
+ <div className="flex items-center gap-4">
150
  <button
151
+ className="md:hidden bg-transparent border-0 text-lg text-muted cursor-pointer flex items-center justify-center z-10 w-10 h-10 rounded-lg hover:bg-background-secondary hover:text-primary transition-all duration-fast"
152
  onClick={toggleMobileMenu}
153
  >
154
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
155
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
156
+ </svg>
157
  </button>
158
+
159
+ {/* Brand Section */}
160
+ <div className="flex items-center gap-3">
161
+ <div className="w-8 h-8 bg-gradient-to-br from-primary to-primary-dark rounded-lg flex items-center justify-center shadow-sm">
162
+ <svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
163
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
164
+ </svg>
165
+ </div>
166
+ <div>
167
+ <h1 className="text-xl font-bold text-primary bg-gradient-to-r from-primary to-primary-dark bg-clip-text text-transparent">Thinking Model Client</h1>
168
+ <p className="text-xs text-muted hidden sm:block">Enterprise AI Assistant</p>
169
+ </div>
170
+ </div>
171
  </div>
172
+
173
+ {/* Controls Section */}
174
+ <div className="flex items-center gap-3">
175
+ {/* Profile Selector */}
176
  <div className="relative">
177
  <button
178
+ className="flex items-center gap-2 py-2 px-4 bg-background-secondary border border-border rounded-lg text-sm text-secondary hover:bg-background-tertiary hover:text-primary hover:border-primary transition-all duration-fast shadow-sm"
179
  onClick={toggleProfileDropdown}
180
  >
181
+ <div className="w-6 h-6 bg-primary rounded-full flex items-center justify-center text-xs text-white font-medium">
182
+ {activeProfile.name.charAt(0).toUpperCase()}
183
+ </div>
184
+ <span className="hidden sm:inline font-medium">{activeProfile.name}</span>
185
+ <svg className={`w-4 h-4 transition-transform duration-fast ${showProfileDropdown ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
186
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
187
+ </svg>
188
  </button>
189
+
190
  {showProfileDropdown && (
191
+ <div className="absolute top-full right-0 w-64 bg-background-elevated border border-border rounded-xl shadow-xl z-50 mt-2 animate-slide-up">
192
+ <div className="p-2">
193
+ <div className="px-3 py-2 text-xs font-semibold text-muted uppercase tracking-wider border-b border-border mb-2">
194
+ Select Profile
 
 
 
 
195
  </div>
196
+ {profiles.map(profile => (
197
+ <button
198
+ key={profile.id}
199
+ className={`w-full flex items-center gap-3 p-3 rounded-lg cursor-pointer transition-all duration-fast text-sm text-left ${
200
+ profile.id === activeProfileId
201
+ ? 'bg-primary-light text-primary font-medium shadow-sm'
202
+ : 'hover:bg-background-secondary text-secondary'
203
+ }`}
204
+ onClick={() => handleProfileSelect(profile.id)}
205
+ >
206
+ <div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-medium ${
207
+ profile.id === activeProfileId ? 'bg-primary text-white' : 'bg-background-tertiary text-muted'
208
+ }`}>
209
+ {profile.name.charAt(0).toUpperCase()}
210
+ </div>
211
+ <div className="flex-1">
212
+ <div className="font-medium">{profile.name}</div>
213
+ <div className="text-xs text-muted truncate">{profile.model}</div>
214
+ </div>
215
+ {profile.id === activeProfileId && (
216
+ <svg className="w-4 h-4 text-primary" fill="currentColor" viewBox="0 0 20 20">
217
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
218
+ </svg>
219
+ )}
220
+ </button>
221
+ ))}
222
+ </div>
223
  </div>
224
  )}
225
  </div>
226
+
227
+ {/* Settings Button */}
228
  <button
229
+ className="flex items-center gap-2 py-2 px-4 bg-background-secondary border border-border rounded-lg text-sm text-secondary hover:bg-background-tertiary hover:text-primary hover:border-primary transition-all duration-fast shadow-sm"
230
  onClick={toggleSettings}
231
  >
232
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
233
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
234
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
235
+ </svg>
236
+ <span className="hidden sm:inline font-medium">Settings</span>
237
  </button>
238
  </div>
239
+ </header>
240
 
241
  <div className="flex flex-1 overflow-hidden">
242
  <div className={`sidebar ${sidebarCollapsed ? 'collapsed' : ''} ${mobileMenuOpen ? 'mobile-open' : ''}`}>
src/components/ChatList.jsx CHANGED
@@ -1,38 +1,146 @@
1
- import React from 'react';
2
 
3
  function ChatList({ chats, currentChat, onSelectChat, onDeleteChat, onCreateNewChat, collapsed, isStreamingChat }) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  return (
5
- <div className="chat-list">
6
- <button className="new-chat" onClick={onCreateNewChat}>
7
- New Chat
8
- </button>
9
- {chats.length === 0 ? (
10
- <div className="empty-state">No conversations yet</div>
11
- ) : (
12
- chats.map(chat => (
13
- <div
14
- key={chat.id}
15
- className={`chat-item ${chat.id === currentChat?.id ? 'active' : ''} ${isStreamingChat && typeof isStreamingChat === 'function' && isStreamingChat(chat.id) ? 'streaming' : ''}`}
16
- onClick={() => onSelectChat(chat.id)}
17
- >
18
- <div className="flex items-center gap-1">
19
- {isStreamingChat && typeof isStreamingChat === 'function' && isStreamingChat(chat.id) && (
20
- <span className="streaming-dot"></span>
21
- )}
22
- <span>{chat.title}</span>
 
 
 
 
23
  </div>
24
- <button
25
- className="delete-btn"
26
- onClick={(e) => {
27
- e.stopPropagation();
28
- onDeleteChat(chat.id);
29
- }}
30
- >
31
- Delete
32
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  </div>
34
- ))
35
- )}
 
 
 
 
 
 
 
36
  </div>
37
  );
38
  }
 
1
+ import React, { useState } from 'react';
2
 
3
  function ChatList({ chats, currentChat, onSelectChat, onDeleteChat, onCreateNewChat, collapsed, isStreamingChat }) {
4
+ const [hoveredChat, setHoveredChat] = useState(null);
5
+
6
+ const formatDate = (timestamp) => {
7
+ const date = new Date(timestamp);
8
+ const now = new Date();
9
+ const diffTime = Math.abs(now - date);
10
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
11
+
12
+ if (diffDays === 1) return 'Today';
13
+ if (diffDays === 2) return 'Yesterday';
14
+ if (diffDays <= 7) return `${diffDays} days ago`;
15
+ return date.toLocaleDateString();
16
+ };
17
+
18
+ const truncateTitle = (title, maxLength = 25) => {
19
+ if (title.length <= maxLength) return title;
20
+ return title.substring(0, maxLength) + '...';
21
+ };
22
+
23
  return (
24
+ <div className="flex flex-col h-full bg-background-elevated">
25
+ {/* New Chat Button */}
26
+ <div className="p-3 border-b border-border">
27
+ <button
28
+ className="w-full flex items-center justify-center gap-2 py-3 px-4 bg-primary hover:bg-primary-hover text-white border-none rounded-xl cursor-pointer text-sm font-medium transition-all duration-fast shadow-sm hover:shadow-md active:scale-[0.98]"
29
+ onClick={onCreateNewChat}
30
+ >
31
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
32
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
33
+ </svg>
34
+ <span>New Conversation</span>
35
+ </button>
36
+ </div>
37
+
38
+ {/* Chat List */}
39
+ <div className="flex-1 overflow-y-auto p-2">
40
+ {chats.length === 0 ? (
41
+ <div className="flex flex-col items-center justify-center py-12 px-4 text-center">
42
+ <div className="w-16 h-16 bg-background-secondary rounded-full flex items-center justify-center mb-4">
43
+ <svg className="w-8 h-8 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
45
+ </svg>
46
  </div>
47
+ <h3 className="text-sm font-medium text-secondary mb-2">No conversations yet</h3>
48
+ <p className="text-xs text-muted">Start a new conversation to get started</p>
49
+ </div>
50
+ ) : (
51
+ <div className="space-y-1">
52
+ {chats.map(chat => {
53
+ const isActive = chat.id === currentChat?.id;
54
+ const isStreaming = isStreamingChat && typeof isStreamingChat === 'function' && isStreamingChat(chat.id);
55
+ const isHovered = hoveredChat === chat.id;
56
+
57
+ return (
58
+ <div
59
+ key={chat.id}
60
+ className={`group relative flex items-center p-3 rounded-lg cursor-pointer transition-all duration-fast ${
61
+ isActive
62
+ ? 'bg-primary-light border border-primary/20 shadow-sm'
63
+ : 'hover:bg-background-secondary border border-transparent'
64
+ }`}
65
+ onClick={() => onSelectChat(chat.id)}
66
+ onMouseEnter={() => setHoveredChat(chat.id)}
67
+ onMouseLeave={() => setHoveredChat(null)}
68
+ >
69
+ {/* Streaming indicator */}
70
+ {isStreaming && (
71
+ <div className="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-info to-primary rounded-r-full animate-pulse" />
72
+ )}
73
+
74
+ <div className="flex-1 min-w-0">
75
+ <div className="flex items-center gap-2 mb-1">
76
+ {/* Chat icon */}
77
+ <div className={`flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center ${
78
+ isActive ? 'bg-primary text-white' : 'bg-background-tertiary text-muted'
79
+ }`}>
80
+ {isStreaming ? (
81
+ <div className="w-2 h-2 bg-current rounded-full animate-pulse" />
82
+ ) : (
83
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
84
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
85
+ </svg>
86
+ )}
87
+ </div>
88
+
89
+ {/* Chat title */}
90
+ <div className="flex-1 min-w-0">
91
+ <h4 className={`text-sm font-medium truncate ${
92
+ isActive ? 'text-primary' : 'text-secondary'
93
+ }`}>
94
+ {truncateTitle(chat.title)}
95
+ </h4>
96
+
97
+ {/* Message count and date */}
98
+ <div className="flex items-center justify-between mt-1">
99
+ <span className="text-xs text-muted">
100
+ {chat.messages?.length || 0} messages
101
+ </span>
102
+ {chat.messages?.length > 0 && (
103
+ <span className="text-xs text-muted">
104
+ {formatDate(chat.messages[chat.messages.length - 1]?.timestamp || Date.now())}
105
+ </span>
106
+ )}
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ {/* Delete button */}
113
+ <button
114
+ className={`flex-shrink-0 ml-2 p-1.5 rounded-md transition-all duration-fast ${
115
+ isHovered || isActive
116
+ ? 'opacity-100 hover:bg-error-light hover:text-error'
117
+ : 'opacity-0 group-hover:opacity-100'
118
+ }`}
119
+ onClick={(e) => {
120
+ e.stopPropagation();
121
+ if (window.confirm('Are you sure you want to delete this conversation?')) {
122
+ onDeleteChat(chat.id);
123
+ }
124
+ }}
125
+ title="Delete conversation"
126
+ >
127
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
128
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
129
+ </svg>
130
+ </button>
131
+ </div>
132
+ );
133
+ })}
134
  </div>
135
+ )}
136
+ </div>
137
+
138
+ {/* Footer */}
139
+ <div className="p-3 border-t border-border">
140
+ <div className="text-xs text-muted text-center">
141
+ {chats.length} conversation{chats.length !== 1 ? 's' : ''}
142
+ </div>
143
+ </div>
144
  </div>
145
  );
146
  }
src/components/ChatWindow.jsx CHANGED
@@ -997,25 +997,7 @@ function ChatWindow({
997
  const mcpTools = getAllTools();
998
 
999
  return (
1000
- <div className="chat-window relative">
1001
- {/* Floating New Chat button */}
1002
- <button
1003
- onClick={() => {
1004
- console.log('Floating new chat button clicked');
1005
- // εœ¨εˆ›ε»Ίζ–°θŠε€©ε‰ζ·»εŠ δΈ€δΊ›ζ—₯εΏ—
1006
- if (typeof onCreateNewChat === 'function') {
1007
- console.log('Calling onCreateNewChat function');
1008
- onCreateNewChat();
1009
- console.log('onCreateNewChat function called');
1010
- } else {
1011
- console.error('onCreateNewChat is not a function');
1012
- }
1013
- }}
1014
- className="floating-new-chat"
1015
- title="Create a new conversation"
1016
- >
1017
- <span className="text-xl">+</span>
1018
- </button>
1019
  <div className="chat-messages">
1020
  {chat && chat.messages && chat.messages.map((message, index) => {
1021
  const parsedMessage = message.role === 'assistant'
 
997
  const mcpTools = getAllTools();
998
 
999
  return (
1000
+ <div className="flex flex-col h-full bg-background-primary">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
  <div className="chat-messages">
1002
  {chat && chat.messages && chat.messages.map((message, index) => {
1003
  const parsedMessage = message.role === 'assistant'
src/index.css CHANGED
@@ -6,28 +6,78 @@
6
  @tailwind components;
7
  @tailwind utilities;
8
 
9
- /* Base styles */
10
  :root {
11
- --primary-color: #3e6ae1;
12
- --primary-hover: #2952c8;
13
- --text-color: #1a1a1a;
14
- --light-text: #4d4d4d;
15
- --lightest-text: #737373;
16
- --border-color: #e5e7eb;
17
- --background-color: #ffffff;
18
- --sidebar-width: 280px;
19
- --header-height: 60px;
20
- --reasoning-background: #f9fafb;
21
- --user-message-bg: #f9f8f6;
22
- --user-message-color: var(--text-color);
23
- --assistant-message-bg: #f9f9f9;
24
- --assistant-message-color: #1a1a1a;
25
- --hover-color: #f5f5f5;
26
- --active-color: #e6f2ff;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  /* Responsive sidebar width */
29
  --mobile-sidebar-width: 100%;
30
- --tablet-sidebar-width: 250px;
 
 
 
 
 
31
  }
32
 
33
  * {
 
6
  @tailwind components;
7
  @tailwind utilities;
8
 
9
+ /* Enterprise Design System */
10
  :root {
11
+ /* Primary Brand Colors */
12
+ --primary-color: #2563eb;
13
+ --primary-hover: #1d4ed8;
14
+ --primary-light: #dbeafe;
15
+ --primary-dark: #1e3a8a;
16
+
17
+ /* Secondary Colors */
18
+ --secondary-color: #64748b;
19
+ --secondary-hover: #475569;
20
+ --secondary-light: #f1f5f9;
21
+
22
+ /* Neutral Palette */
23
+ --text-color: #0f172a;
24
+ --text-secondary: #334155;
25
+ --text-muted: #64748b;
26
+ --text-light: #94a3b8;
27
+ --text-disabled: #cbd5e1;
28
+
29
+ /* Background System */
30
+ --background-primary: #ffffff;
31
+ --background-secondary: #f8fafc;
32
+ --background-tertiary: #f1f5f9;
33
+ --background-elevated: #ffffff;
34
+
35
+ /* Border System */
36
+ --border-color: #e2e8f0;
37
+ --border-light: #f1f5f9;
38
+ --border-dark: #cbd5e1;
39
+ --border-accent: #3b82f6;
40
+
41
+ /* Status Colors */
42
+ --success-color: #059669;
43
+ --success-light: #d1fae5;
44
+ --warning-color: #d97706;
45
+ --warning-light: #fef3c7;
46
+ --error-color: #dc2626;
47
+ --error-light: #fee2e2;
48
+ --info-color: #0284c7;
49
+ --info-light: #e0f2fe;
50
+
51
+ /* Layout Variables */
52
+ --sidebar-width: 320px;
53
+ --header-height: 64px;
54
+ --border-radius: 8px;
55
+ --border-radius-lg: 12px;
56
+ --border-radius-xl: 16px;
57
+
58
+ /* Shadow System */
59
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
60
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
61
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
62
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
63
+
64
+ /* Message Specific */
65
+ --reasoning-background: var(--background-tertiary);
66
+ --user-message-bg: var(--primary-color);
67
+ --user-message-color: #ffffff;
68
+ --assistant-message-bg: var(--background-elevated);
69
+ --assistant-message-color: var(--text-color);
70
+ --hover-color: var(--background-secondary);
71
+ --active-color: var(--primary-light);
72
 
73
  /* Responsive sidebar width */
74
  --mobile-sidebar-width: 100%;
75
+ --tablet-sidebar-width: 280px;
76
+
77
+ /* Animation System */
78
+ --transition-fast: 150ms ease-out;
79
+ --transition-normal: 250ms ease-out;
80
+ --transition-slow: 350ms ease-out;
81
  }
82
 
83
  * {
tailwind.config.js CHANGED
@@ -7,13 +7,62 @@ export default {
7
  theme: {
8
  extend: {
9
  colors: {
10
- primary: 'var(--primary-color)',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  'primary-hover': 'var(--primary-hover)',
12
- 'text': 'var(--text-color)',
13
- 'light-text': 'var(--light-text)',
14
- 'lightest-text': 'var(--lightest-text)',
15
- 'border': 'var(--border-color)',
16
- 'background': 'var(--background-color)',
17
  'reasoning-bg': 'var(--reasoning-background)',
18
  'user-message-bg': 'var(--user-message-bg)',
19
  'user-message-color': 'var(--user-message-color)',
@@ -26,14 +75,58 @@ export default {
26
  'sidebar': 'var(--sidebar-width)',
27
  'header': 'var(--header-height)',
28
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  animation: {
30
  'blink': 'blink 1s infinite',
 
 
 
 
31
  },
32
  keyframes: {
33
  blink: {
34
  '0%, 100%': { opacity: 1 },
35
  '50%': { opacity: 0 },
36
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  },
38
  },
39
  },
 
7
  theme: {
8
  extend: {
9
  colors: {
10
+ // Primary brand colors
11
+ primary: {
12
+ DEFAULT: 'var(--primary-color)',
13
+ hover: 'var(--primary-hover)',
14
+ light: 'var(--primary-light)',
15
+ dark: 'var(--primary-dark)',
16
+ },
17
+ // Secondary colors
18
+ secondary: {
19
+ DEFAULT: 'var(--secondary-color)',
20
+ hover: 'var(--secondary-hover)',
21
+ light: 'var(--secondary-light)',
22
+ },
23
+ // Text colors
24
+ text: {
25
+ DEFAULT: 'var(--text-color)',
26
+ secondary: 'var(--text-secondary)',
27
+ muted: 'var(--text-muted)',
28
+ light: 'var(--text-light)',
29
+ disabled: 'var(--text-disabled)',
30
+ },
31
+ // Background colors
32
+ background: {
33
+ DEFAULT: 'var(--background-primary)',
34
+ secondary: 'var(--background-secondary)',
35
+ tertiary: 'var(--background-tertiary)',
36
+ elevated: 'var(--background-elevated)',
37
+ },
38
+ // Border colors
39
+ border: {
40
+ DEFAULT: 'var(--border-color)',
41
+ light: 'var(--border-light)',
42
+ dark: 'var(--border-dark)',
43
+ accent: 'var(--border-accent)',
44
+ },
45
+ // Status colors
46
+ success: {
47
+ DEFAULT: 'var(--success-color)',
48
+ light: 'var(--success-light)',
49
+ },
50
+ warning: {
51
+ DEFAULT: 'var(--warning-color)',
52
+ light: 'var(--warning-light)',
53
+ },
54
+ error: {
55
+ DEFAULT: 'var(--error-color)',
56
+ light: 'var(--error-light)',
57
+ },
58
+ info: {
59
+ DEFAULT: 'var(--info-color)',
60
+ light: 'var(--info-light)',
61
+ },
62
+ // Legacy color mappings (for backwards compatibility)
63
  'primary-hover': 'var(--primary-hover)',
64
+ 'light-text': 'var(--text-light)',
65
+ 'lightest-text': 'var(--text-disabled)',
 
 
 
66
  'reasoning-bg': 'var(--reasoning-background)',
67
  'user-message-bg': 'var(--user-message-bg)',
68
  'user-message-color': 'var(--user-message-color)',
 
75
  'sidebar': 'var(--sidebar-width)',
76
  'header': 'var(--header-height)',
77
  },
78
+ borderRadius: {
79
+ 'DEFAULT': 'var(--border-radius)',
80
+ 'lg': 'var(--border-radius-lg)',
81
+ 'xl': 'var(--border-radius-xl)',
82
+ },
83
+ boxShadow: {
84
+ 'sm': 'var(--shadow-sm)',
85
+ 'DEFAULT': 'var(--shadow-md)',
86
+ 'md': 'var(--shadow-md)',
87
+ 'lg': 'var(--shadow-lg)',
88
+ 'xl': 'var(--shadow-xl)',
89
+ },
90
+ transitionDuration: {
91
+ 'fast': 'var(--transition-fast)',
92
+ 'normal': 'var(--transition-normal)',
93
+ 'slow': 'var(--transition-slow)',
94
+ },
95
  animation: {
96
  'blink': 'blink 1s infinite',
97
+ 'pulse-slow': 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
98
+ 'fade-in': 'fadeIn 0.5s ease-out',
99
+ 'slide-up': 'slideUp 0.3s ease-out',
100
+ 'scale-in': 'scaleIn 0.2s ease-out',
101
  },
102
  keyframes: {
103
  blink: {
104
  '0%, 100%': { opacity: 1 },
105
  '50%': { opacity: 0 },
106
  },
107
+ fadeIn: {
108
+ '0%': { opacity: 0 },
109
+ '100%': { opacity: 1 },
110
+ },
111
+ slideUp: {
112
+ '0%': { transform: 'translateY(10px)', opacity: 0 },
113
+ '100%': { transform: 'translateY(0)', opacity: 1 },
114
+ },
115
+ scaleIn: {
116
+ '0%': { transform: 'scale(0.95)', opacity: 0 },
117
+ '100%': { transform: 'scale(1)', opacity: 1 },
118
+ },
119
+ },
120
+ fontFamily: {
121
+ 'sans': ['-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif'],
122
+ },
123
+ fontSize: {
124
+ 'xs': ['0.75rem', { lineHeight: '1rem' }],
125
+ 'sm': ['0.875rem', { lineHeight: '1.25rem' }],
126
+ 'base': ['1rem', { lineHeight: '1.5rem' }],
127
+ 'lg': ['1.125rem', { lineHeight: '1.75rem' }],
128
+ 'xl': ['1.25rem', { lineHeight: '1.75rem' }],
129
+ '2xl': ['1.5rem', { lineHeight: '2rem' }],
130
  },
131
  },
132
  },
test-xsai.js ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ import { streamText } from '@xsai/stream-text';
4
+ import { generateText } from '@xsai/generate-text';
5
+ import { extractReasoningStream } from '@xsai/utils-reasoning';
6
+
7
+ // Test configuration - replace with your actual API details
8
+ const testConfig = {
9
+ baseURL: 'https://api.deepseek.com/v1', // Example - replace with your API endpoint
10
+ apiKey: 'your-api-key-here', // Replace with your actual API key
11
+ model: 'deepseek-r1' // Replace with your model
12
+ };
13
+
14
+ async function testGenerateText() {
15
+ console.log('πŸ§ͺ Testing xsai generateText integration...\n');
16
+
17
+ try {
18
+ const { text } = await generateText({
19
+ apiKey: testConfig.apiKey,
20
+ baseURL: testConfig.baseURL,
21
+ model: testConfig.model,
22
+ messages: [{
23
+ role: 'user',
24
+ content: 'Summarize this in 3-5 words: Hello, how are you today? I am doing well, thanks for asking!'
25
+ }],
26
+ temperature: 0.2,
27
+ max_tokens: 20
28
+ });
29
+
30
+ console.log('βœ… GenerateText test successful!');
31
+ console.log('πŸ“ Summary result:', text.trim());
32
+
33
+ } catch (error) {
34
+ console.error('❌ GenerateText test failed:', error.message);
35
+ }
36
+ }
37
+
38
+ async function testStreamText() {
39
+ console.log('\nπŸ§ͺ Testing xsai streamText with reasoning extraction...\n');
40
+
41
+ try {
42
+ const { textStream } = await streamText({
43
+ apiKey: testConfig.apiKey,
44
+ baseURL: testConfig.baseURL,
45
+ model: testConfig.model,
46
+ messages: [
47
+ { role: 'system', content: 'You are a helpful assistant. Use <think></think> tags to show your reasoning process.' },
48
+ { role: 'user', content: 'Why is the sky blue? Please think through this step by step.' }
49
+ ]
50
+ });
51
+
52
+ // Extract reasoning and content streams
53
+ const { reasoningStream, textStream: contentStream } = extractReasoningStream(textStream);
54
+
55
+ console.log('🧠 Reasoning process:');
56
+ console.log('='.repeat(50));
57
+ let reasoningText = '';
58
+ for await (const chunk of reasoningStream) {
59
+ process.stdout.write(chunk);
60
+ reasoningText += chunk;
61
+ }
62
+
63
+ console.log('\n' + '='.repeat(50));
64
+ console.log('\nπŸ’¬ Final answer:');
65
+ console.log('='.repeat(50));
66
+ for await (const chunk of contentStream) {
67
+ process.stdout.write(chunk);
68
+ }
69
+
70
+ console.log('\n' + '='.repeat(50));
71
+ console.log('\nβœ… StreamText test successful!');
72
+
73
+ } catch (error) {
74
+ console.error('❌ StreamText test failed:', error.message);
75
+ }
76
+ }
77
+
78
+ async function runTests() {
79
+ console.log('πŸš€ Testing xsai integration for thinking-model-client\n');
80
+
81
+ // Check if configuration is set
82
+ if (testConfig.apiKey === 'your-api-key-here') {
83
+ console.log('⚠️ Please update the testConfig in this file with your actual API details before running tests.');
84
+ console.log('πŸ“ Edit the testConfig object at the top of this file.');
85
+ return;
86
+ }
87
+
88
+ await testGenerateText();
89
+ await testStreamText();
90
+
91
+ console.log('\nπŸŽ‰ All tests completed!');
92
+ }
93
+
94
+ runTests().catch(console.error);