create-gentiq-app 0.7.26 → 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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-gentiq-app",
3
- "version": "0.7.26",
3
+ "version": "0.7.28",
4
4
  "description": "CLI to scaffold a new Gentiq application",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- pass
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
- # @agent.tool
30
- # async def my_tool(ctx: RunContext[AgentDeps[AppContext]], param: str) -> str:
31
- # """
32
- # A tool description goes here.
33
- # """
34
- # return f"Processed {param}"
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
- # 4. Initialize the core backend framework application
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
- # 5. (Optional) Extend the API with custom routes protected by Gentiq's authentication
91
+ # 6. Extend the API with custom routes protected by Gentiq's authentication
51
92
  custom_router = APIRouter()
52
93
 
53
- # @custom_router.get("/my-custom-endpoint")
54
- # async def my_endpoint(user: Annotated[User, Depends(get_current_user)]):
55
- # return {"message": f"Hello {user.name}!"}
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
 
@@ -5,7 +5,7 @@ description = ""
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
7
  dependencies = [
8
- "gentiq",
8
+ "gentiq[engines]",
9
9
  ]
10
10
 
11
11
  [tool.ruff]
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "gentiq": "workspace:*",
14
+ "lucide-react": "^0.542.0",
14
15
  "react": "^19.2.0",
15
16
  "react-dom": "^19.2.0",
16
17
  "react-router-dom": "^7.13.1"
@@ -1,8 +1,101 @@
1
1
  import { BrowserRouter, Route, Routes } from 'react-router-dom'
2
- import { GentiqProvider, ChatUI, AdminPanel, RequireAuth, UserLoginPage, SharedChatView } from 'gentiq'
3
- import type { GentiqComponents } from 'gentiq'
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
- // Add your custom tool components here:
25
- // toolComponents: {
26
- // my_tool_name: MyCustomToolComponent,
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
- // Example: Add custom fields to user profiles and signup
43
- // userMetadataFields: [
44
- // { key: 'grade', label: 'settings:profile.grade', type: 'select', options: [...], showInSignup: true, showInProfile: true }
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.welcome.title",
60
- prompts: [] // Add initial quick prompts here
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.input_placeholder",
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 message...',
198
+ input_placeholder: 'Type your query to the AI...',
199
+ disclaimer: "AI can make mistakes! Check important information.",
83
200
  welcome: {
84
- title: "Hello! How can I help you today?",
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
  }}