create-gentiq-app 0.7.27 → 0.7.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import random
|
|
1
3
|
from dataclasses import dataclass
|
|
4
|
+
from typing import Annotated
|
|
2
5
|
|
|
3
|
-
from fastapi import APIRouter
|
|
4
|
-
from pydantic_ai import Agent
|
|
6
|
+
from fastapi import APIRouter, Depends
|
|
7
|
+
from pydantic_ai import Agent, RunContext
|
|
5
8
|
|
|
6
9
|
from gentiq import (
|
|
7
10
|
AgentDeps,
|
|
8
11
|
GentiqApp,
|
|
12
|
+
ProgressUpdateEvent,
|
|
13
|
+
User,
|
|
9
14
|
create_title_agent,
|
|
15
|
+
get_current_user,
|
|
10
16
|
)
|
|
11
17
|
|
|
12
18
|
|
|
@@ -14,7 +20,7 @@ from gentiq import (
|
|
|
14
20
|
@dataclass
|
|
15
21
|
class AppContext:
|
|
16
22
|
# Add any application-specific state here (e.g. user preferences, active project Id)
|
|
17
|
-
|
|
23
|
+
user_city: str = "London"
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
# 2. Define custom agent with specified context type
|
|
@@ -25,16 +31,51 @@ You are a helpful AI assistant.
|
|
|
25
31
|
agent = Agent[AgentDeps[AppContext]](name="agent", instructions=instructions, model="openai:gpt-5.1")
|
|
26
32
|
|
|
27
33
|
|
|
28
|
-
# 3. Add custom tools
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
# 3. Add custom tools that leverage the user context via deps.context
|
|
35
|
+
@agent.tool
|
|
36
|
+
async def get_weather(ctx: RunContext[AgentDeps[AppContext]], city: str | None = None) -> dict:
|
|
37
|
+
"""
|
|
38
|
+
Get weather of the specified city or if not specified, the user's location
|
|
39
|
+
"""
|
|
40
|
+
city = city or ctx.deps.context.user_city
|
|
35
41
|
|
|
42
|
+
await ctx.deps.stream(
|
|
43
|
+
ProgressUpdateEvent(
|
|
44
|
+
tool_name=ctx.tool_name or "get_weather",
|
|
45
|
+
status="running",
|
|
46
|
+
message=f"Getting weather for {city}...",
|
|
47
|
+
)
|
|
48
|
+
)
|
|
36
49
|
|
|
37
|
-
#
|
|
50
|
+
# simulate network delay
|
|
51
|
+
await asyncio.sleep(1)
|
|
52
|
+
|
|
53
|
+
choices = ["sunny", "rainy", "snowy", "windy"]
|
|
54
|
+
condition = random.choice(choices)
|
|
55
|
+
|
|
56
|
+
await ctx.deps.stream(
|
|
57
|
+
ProgressUpdateEvent(
|
|
58
|
+
tool_name=ctx.tool_name or "get_weather",
|
|
59
|
+
status="completed",
|
|
60
|
+
message=f"Weather fetched for {city}",
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"city": city,
|
|
66
|
+
"condition": condition,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# 4. Add user's info in the instructions
|
|
71
|
+
@agent.instructions
|
|
72
|
+
def add_user_info(ctx: RunContext[AgentDeps[AppContext]]) -> str | None:
|
|
73
|
+
user_info = "Here's additional info about the current user you're talking to:\n"
|
|
74
|
+
user_info += f"User's name is `{ctx.deps.user.name} {ctx.deps.user.surname}`"
|
|
75
|
+
return user_info
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# 5. Initialize the core backend framework application with the agent and its context
|
|
38
79
|
# The framework automatically injects internal Stores into AgentDeps
|
|
39
80
|
app = GentiqApp[AppContext](
|
|
40
81
|
agent,
|
|
@@ -47,12 +88,14 @@ app = GentiqApp[AppContext](
|
|
|
47
88
|
)
|
|
48
89
|
|
|
49
90
|
|
|
50
|
-
#
|
|
91
|
+
# 6. Extend the API with custom routes protected by Gentiq's authentication
|
|
51
92
|
custom_router = APIRouter()
|
|
52
93
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
94
|
+
|
|
95
|
+
@custom_router.get("/my-custom-endpoint")
|
|
96
|
+
async def my_endpoint(user: Annotated[User, Depends(get_current_user)]):
|
|
97
|
+
return {"message": f"Hello {user.name}, this is a custom extension endpoint."}
|
|
98
|
+
|
|
56
99
|
|
|
57
100
|
app.add_router(custom_router, prefix="/ext", tags=["Extensions"])
|
|
58
101
|
|
|
@@ -1,8 +1,101 @@
|
|
|
1
1
|
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
|
2
|
-
import { GentiqProvider, ChatUI,
|
|
3
|
-
import
|
|
2
|
+
import { GentiqProvider, ChatUI, RequireAuth, UserLoginPage, SharedChatView } from 'gentiq'
|
|
3
|
+
import { AdminPanel } from 'gentiq/admin'
|
|
4
|
+
import type { ToolCallComponentProps, GentiqComponents } from 'gentiq'
|
|
4
5
|
import 'gentiq/style.css'
|
|
5
6
|
import './index.css'
|
|
7
|
+
import { Sun, CloudRain, Snowflake, Wind, Cloud, Sparkles, Lightbulb, Zap } from 'lucide-react'
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Custom tool component: WeatherCard
|
|
11
|
+
// Receives `part`, `message`, and `chat` — see ToolCallComponentProps.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
const WeatherCard = ({ part }: ToolCallComponentProps) => {
|
|
14
|
+
try {
|
|
15
|
+
const result = (part as any).result || (part as any).output
|
|
16
|
+
if (!result) return null
|
|
17
|
+
|
|
18
|
+
let data: any
|
|
19
|
+
if (typeof result === 'string') {
|
|
20
|
+
try {
|
|
21
|
+
data = JSON.parse(result)
|
|
22
|
+
} catch {
|
|
23
|
+
return (
|
|
24
|
+
<div className="my-2 p-3 bg-destructive/10 text-destructive rounded-xl text-[10px] font-mono border border-destructive/20 flex items-center gap-2">
|
|
25
|
+
<span className="w-2 h-2 rounded-full bg-destructive animate-pulse" />
|
|
26
|
+
Invalid Weather Data: {result}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
data = result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!data || typeof data !== 'object') return null
|
|
35
|
+
const { city, condition } = data
|
|
36
|
+
|
|
37
|
+
const getIcon = (size = 24) => {
|
|
38
|
+
switch (condition) {
|
|
39
|
+
case 'sunny': return <Sun size={size} strokeWidth={2.5} className="text-amber-100" />
|
|
40
|
+
case 'rainy': return <CloudRain size={size} strokeWidth={2.5} className="text-blue-100" />
|
|
41
|
+
case 'snowy': return <Snowflake size={size} strokeWidth={2.5} className="text-slate-600" />
|
|
42
|
+
case 'windy': return <Wind size={size} strokeWidth={2.5} className="text-emerald-100" />
|
|
43
|
+
default: return <Cloud size={size} strokeWidth={2.5} className="text-indigo-100" />
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const getColors = () => {
|
|
48
|
+
switch (condition) {
|
|
49
|
+
case 'sunny': return 'from-amber-400 via-orange-500 to-rose-500'
|
|
50
|
+
case 'rainy': return 'from-blue-600 via-indigo-700 to-violet-800'
|
|
51
|
+
case 'snowy': return 'from-slate-100 via-blue-50 to-slate-200'
|
|
52
|
+
case 'windy': return 'from-emerald-400 via-teal-500 to-cyan-600'
|
|
53
|
+
default: return 'from-indigo-500 via-purple-500 to-pink-500'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const isLight = condition === 'snowy'
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className={`my-1.5 overflow-hidden rounded-xl shadow-lg bg-gradient-to-br ${getColors()} p-3 ${isLight ? 'text-slate-800' : 'text-white'} animate-in fade-in slide-in-from-bottom-1 duration-500 border border-white/10 backdrop-blur-md max-w-sm w-fit min-w-[280px]`}>
|
|
61
|
+
<div className="flex items-center gap-4">
|
|
62
|
+
<div className="relative group flex-shrink-0">
|
|
63
|
+
<div className={`absolute inset-0 blur-xl opacity-30 scale-125 ${isLight ? 'bg-slate-400' : 'bg-white'}`}>
|
|
64
|
+
{getIcon(32)}
|
|
65
|
+
</div>
|
|
66
|
+
<div className="relative drop-shadow-md">{getIcon(36)}</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="flex-grow min-w-0">
|
|
70
|
+
<div className="flex items-baseline justify-between gap-2">
|
|
71
|
+
<h3 className="text-base font-black tracking-tight truncate leading-tight">{city}</h3>
|
|
72
|
+
<span className="text-xl font-black leading-none tabular-nums shrink-0">24°C</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex items-center gap-3 mt-1">
|
|
75
|
+
<div className="flex items-center gap-1.5 min-w-0">
|
|
76
|
+
<div className={`w-1 h-1 rounded-full shrink-0 ${isLight ? 'bg-slate-500/50' : 'bg-white/50'}`} />
|
|
77
|
+
<p className="capitalize text-[10px] font-bold tracking-wide opacity-80 truncate">{condition}</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div className={`flex items-center gap-2 border-l ${isLight ? 'border-slate-800/10' : 'border-white/10'} pl-3 shrink-0`}>
|
|
80
|
+
<div className="flex flex-col items-center">
|
|
81
|
+
<span className={`text-[8px] uppercase ${isLight ? 'opacity-40' : 'opacity-60'} font-black leading-none`}>Hum</span>
|
|
82
|
+
<span className="text-[10px] font-black leading-tight">42%</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="flex flex-col items-center">
|
|
85
|
+
<span className={`text-[8px] uppercase ${isLight ? 'opacity-40' : 'opacity-60'} font-black leading-none`}>UV</span>
|
|
86
|
+
<span className="text-[10px] font-black leading-tight">Low</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error('WeatherCard error:', err)
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
6
99
|
|
|
7
100
|
// ---------------------------------------------------------------------------
|
|
8
101
|
// Auth adapter
|
|
@@ -21,10 +114,9 @@ const LocalStorageAuthAdapter = {
|
|
|
21
114
|
// Component overrides registered through the unified GentiqComponents map.
|
|
22
115
|
// ---------------------------------------------------------------------------
|
|
23
116
|
const appComponents: GentiqComponents = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// },
|
|
117
|
+
toolComponents: {
|
|
118
|
+
get_weather: WeatherCard,
|
|
119
|
+
},
|
|
28
120
|
}
|
|
29
121
|
|
|
30
122
|
// ---------------------------------------------------------------------------
|
|
@@ -39,10 +131,23 @@ export default function App() {
|
|
|
39
131
|
favicon: "/favicon.svg",
|
|
40
132
|
basePath: "/chat",
|
|
41
133
|
cacheNamespace: "my_app_cache",
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
134
|
+
showToolDetails: false,
|
|
135
|
+
showSettings: true,
|
|
136
|
+
userMetadataFields: [
|
|
137
|
+
{
|
|
138
|
+
key: 'job_position',
|
|
139
|
+
label: 'settings:profile.job_position',
|
|
140
|
+
type: 'select',
|
|
141
|
+
options: [
|
|
142
|
+
{ label: 'settings:profile.job_position_options.manager', value: 'manager' },
|
|
143
|
+
{ label: 'settings:profile.job_position_options.developer', value: 'developer' },
|
|
144
|
+
{ label: 'settings:profile.job_position_options.other', value: 'other' }
|
|
145
|
+
],
|
|
146
|
+
showInSignup: true,
|
|
147
|
+
showInProfile: true,
|
|
148
|
+
required: true,
|
|
149
|
+
}
|
|
150
|
+
]
|
|
46
151
|
}}
|
|
47
152
|
api={{
|
|
48
153
|
authAdapter: LocalStorageAuthAdapter,
|
|
@@ -56,15 +161,19 @@ export default function App() {
|
|
|
56
161
|
showShare: true
|
|
57
162
|
}}
|
|
58
163
|
welcome={{
|
|
59
|
-
greeting: "chat
|
|
60
|
-
prompts: [
|
|
164
|
+
greeting: "chat:welcome.greeting",
|
|
165
|
+
prompts: [
|
|
166
|
+
{ text: 'chat:welcome.suggestion.features', icon: Sparkles },
|
|
167
|
+
{ text: 'chat:welcome.suggestion.tools', icon: Zap },
|
|
168
|
+
{ text: 'chat:welcome.suggestion.weather', icon: Sun }
|
|
169
|
+
]
|
|
61
170
|
}}
|
|
62
171
|
threadActions={{
|
|
63
172
|
feedback: true,
|
|
64
173
|
retry: true,
|
|
65
174
|
}}
|
|
66
175
|
composer={{
|
|
67
|
-
placeholder: "chat
|
|
176
|
+
placeholder: "chat:input_placeholder",
|
|
68
177
|
attachments: {
|
|
69
178
|
enabled: true,
|
|
70
179
|
maxSize: 10 * 1024 * 1024, // 10MB
|
|
@@ -72,18 +181,95 @@ export default function App() {
|
|
|
72
181
|
}
|
|
73
182
|
}}
|
|
74
183
|
theme={{
|
|
184
|
+
typography: {
|
|
185
|
+
fontFamily: {
|
|
186
|
+
en: 'Lato',
|
|
187
|
+
fa: 'Vazirmatn'
|
|
188
|
+
}
|
|
189
|
+
},
|
|
75
190
|
radius: 16,
|
|
76
191
|
accent: '#3c85f1',
|
|
77
192
|
}}
|
|
193
|
+
disclaimer="chat:disclaimer"
|
|
78
194
|
i18n={{
|
|
79
195
|
resources: {
|
|
80
196
|
en: {
|
|
81
197
|
chat: {
|
|
82
|
-
input_placeholder: 'Type your
|
|
198
|
+
input_placeholder: 'Type your query to the AI...',
|
|
199
|
+
disclaimer: "AI can make mistakes! Check important information.",
|
|
83
200
|
welcome: {
|
|
84
|
-
|
|
201
|
+
greeting: "How can I help you today?",
|
|
202
|
+
suggestion: {
|
|
203
|
+
features: "What things can you do?",
|
|
204
|
+
tools: "Tell me an unbelievable fact!",
|
|
205
|
+
weather: "How is the weather in my area?"
|
|
206
|
+
}
|
|
85
207
|
}
|
|
86
208
|
},
|
|
209
|
+
settings: {
|
|
210
|
+
profile: {
|
|
211
|
+
job_position: "Job Position",
|
|
212
|
+
job_position_options: {
|
|
213
|
+
manager: "Manager",
|
|
214
|
+
developer: "Developer",
|
|
215
|
+
other: "Other"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
fa: {
|
|
221
|
+
chat: {
|
|
222
|
+
input_placeholder: 'هر چی میخوای ازم بپرس',
|
|
223
|
+
disclaimer: "هوش مصنوعی میتواند اشتباه کند! اطلاعات مهم را بررسی کنید.",
|
|
224
|
+
welcome: {
|
|
225
|
+
greeting: "چطور میتونم امروز بهت کمک کنم؟",
|
|
226
|
+
suggestion: {
|
|
227
|
+
features: "چه کارهایی میتونی انجام بدی؟",
|
|
228
|
+
tools: "یه حقیقت باورنکردنی بگو!",
|
|
229
|
+
weather: "هوا سمت من چطوره؟"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
settings: {
|
|
234
|
+
profile: {
|
|
235
|
+
job_position: "سمت شغلی",
|
|
236
|
+
job_position_options: {
|
|
237
|
+
manager: "مدیر",
|
|
238
|
+
developer: "توسعهدهنده",
|
|
239
|
+
other: "سایر موارد"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
}}
|
|
246
|
+
settings={{
|
|
247
|
+
sections: {
|
|
248
|
+
general: {
|
|
249
|
+
enabled: true,
|
|
250
|
+
fields: {
|
|
251
|
+
theme: 'editable',
|
|
252
|
+
accent: 'editable',
|
|
253
|
+
radius: 'editable',
|
|
254
|
+
language: 'editable',
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
profile: {
|
|
258
|
+
enabled: true,
|
|
259
|
+
fields: {
|
|
260
|
+
name: 'editable',
|
|
261
|
+
surname: 'editable',
|
|
262
|
+
phone: 'editable',
|
|
263
|
+
password: 'editable',
|
|
264
|
+
job_position: 'editable',
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
account: {
|
|
268
|
+
enabled: true,
|
|
269
|
+
fields: {
|
|
270
|
+
balance: 'editable',
|
|
271
|
+
logout: 'editable',
|
|
272
|
+
}
|
|
87
273
|
}
|
|
88
274
|
}
|
|
89
275
|
}}
|