Flow - New Prompt » History » Version 3
Tomislav Pleše, 10/11/2025 08:46 PM
| 1 | 1 | Tomislav Pleše | # Flow - New Prompt |
|---|---|---|---|
| 2 | 2 | Tomislav Pleše | |
| 3 | |||
| 4 | 3 | Tomislav Pleše | 1. Application Entry Point |
| 5 | main.dart |
||
| 6 | main() → runApp(MyApp()) |
||
| 7 | 2. Home Screen Initialization |
||
| 8 | HomeScreen (StatefulWidget) |
||
| 9 | Location: lib/screens/home_screen.dart |
||
| 10 | State Initialization (HomeScreenState.initState()) |
||
| 11 | 1. Initialize PromptArgs (empty state) |
||
| 12 | 2. Create BackendService instance |
||
| 13 | 3. Create PointManager(backendService) |
||
| 14 | 4. Create TreeViewManager |
||
| 15 | 5. Create ShardManager(pointManager, backendService) |
||
| 16 | 6. Create ThreadManager(pointManager, shardManager, backendService, threadMap, onMapUpdated) |
||
| 17 | 7. Create ContextManager(threadManager, apiService, threadMap) |
||
| 18 | 8. Create HomeScreenManager(managers..., callbacks...) |
||
| 19 | 9. Initialize UI state variables: |
||
| 20 | - threadMap: Map<String, Point> |
||
| 21 | - treeViewList: List<Point> |
||
| 22 | - loadingExchanges: Set<String> |
||
| 23 | - isLoading: bool |
||
| 24 | - errorMessage: String? |
||
| 25 | 3. User Input Flow |
||
| 26 | PromptInputField (StatefulWidget) |
||
| 27 | Location: lib/ui/widgets/prompt_input_field.dart |
||
| 28 | User Types Prompt |
||
| 29 | TextField(controller: _textController) |
||
| 30 | ↓ |
||
| 31 | User presses Send button or Enter |
||
| 32 | ↓ |
||
| 33 | _handleSendRequest() |
||
| 34 | ↓ |
||
| 35 | widget.onPrompt(text) → calls HomeScreen.onPrompt() |
||
| 36 | 4. Prompt Processing Flow |
||
| 37 | HomeScreen.onPrompt(String promptText) |
||
| 38 | Async method that orchestrates the entire prompt handling |
||
| 39 | 1. Clear error messages (setState) |
||
| 40 | 2. Determine if this is a sub-prompt or regular prompt |
||
| 41 | 3. Create/update PromptArgs: |
||
| 42 | - For regular prompt: use last Point in treeViewList as parent |
||
| 43 | - For sub-prompt: use existing promptArgs with shard info |
||
| 44 | 4. Call HomeScreenManager.handlePrompt(...) |
||
| 45 | 5. Clear promptArgs (setState) |
||
| 46 | 6. Handle errors with setState and error display |
||
| 47 | 5. Home Screen Manager Processing |
||
| 48 | HomeScreenManager.handlePrompt(...) |
||
| 49 | Location: lib/managers/home_screen_manager.dart |
||
| 50 | 1. Validate prompt text (not empty) |
||
| 51 | 2. onLoadingStateChanged(true) → updates UI loading state |
||
| 52 | 3. ThreadManager.updateThreadMap(promptText, promptArgs) |
||
| 53 | → Returns: Point newPoint |
||
| 54 | 4. Create AiModelProperties (model config) |
||
| 55 | 5. ContextManager.createPromptContext(newPoint) |
||
| 56 | → Returns: List<RequestMessage> promptContextMessages |
||
| 57 | 6. Track loading state for this exchange: |
||
| 58 | - Extract exchangeId from newPoint.exchangesList.last |
||
| 59 | - Add to loadingExchanges set |
||
| 60 | - updateLoadingExchanges callback → updates HomeScreen state |
||
| 61 | 7. ApiService.sendPromptToAi(pointId, promptContextMessages, aiModelProperties) |
||
| 62 | → Returns: Point updatedNewPoint (with AI response) |
||
| 63 | 8. Remove exchangeId from loadingExchanges |
||
| 64 | 9. Update threadMap[updatedNewPoint.id] = updatedNewPoint |
||
| 65 | 10. onMapUpdated() callback |
||
| 66 | 11. populateTreeViewList(treeViewList, updateTreeViewList) |
||
| 67 | 12. onClearPromptInput() → clears input field |
||
| 68 | 13. onLoadingStateChanged(false) |
||
| 69 | 6. Thread Map Update (Core Logic) |
||
| 70 | ThreadManager.updateThreadMap(...) |
||
| 71 | Location: lib/managers/thread_manager.dart |
||
| 72 | 1. PointManager.createNewPoint(promptArgs, promptText) |
||
| 73 | → Returns: Point newPoint (with prompt, no response yet) |
||
| 74 | |||
| 75 | 2. IF newPoint.id == newPoint.parentPointId: |
||
| 76 | → Root point: Add to threadMap and return |
||
| 77 | |||
| 78 | 3. ELSE: Get parent point |
||
| 79 | - IF NOT in threadMap: |
||
| 80 | → PointManager.getParentPoint(fetchArgs, newPoint.id) |
||
| 81 | → Cache parent in threadMap |
||
| 82 | - ELSE: |
||
| 83 | → Retrieve parent from threadMap |
||
| 84 | |||
| 85 | 4. Decision Branch: |
||
| 86 | |||
| 87 | A. IF promptArgs.isShardChild AND selectedText exists: |
||
| 88 | **SHARDING FLOW** (Sub-prompt) |
||
| 89 | ↓ |
||
| 90 | ShardManager.addShardToParentPoint(newPoint.id, parentPoint, promptArgs) |
||
| 91 | → Returns: Point updatedParentPoint (with new Shard added) |
||
| 92 | ↓ |
||
| 93 | Extract parentShardId from updatedParentPoint.shardsList |
||
| 94 | ↓ |
||
| 95 | newPoint.copyWith(parentShardId: parentShardId) |
||
| 96 | ↓ |
||
| 97 | Update threadMap with both points |
||
| 98 | ↓ |
||
| 99 | onMapUpdated() callback |
||
| 100 | |||
| 101 | B. ELSE: |
||
| 102 | **REGULAR FLOW** (Continue conversation) |
||
| 103 | ↓ |
||
| 104 | PointManager.updateParentPoint(newPoint.id, parentPoint) |
||
| 105 | → Returns: Point updatedParentPoint (with newPoint.id in pointChildren) |
||
| 106 | ↓ |
||
| 107 | Update threadMap with both points |
||
| 108 | ↓ |
||
| 109 | onMapUpdated() callback |
||
| 110 | |||
| 111 | 5. Return newPoint (or updatedNewPoint) |
||
| 112 | 7. Tree View Update |
||
| 113 | HomeScreenManager.populateTreeViewList(...) |
||
| 114 | TreeViewManager.updateTreeViewList(threadMap, treeViewList) |
||
| 115 | → Returns: List<Point> newTreeViewList |
||
| 116 | ↓ |
||
| 117 | updateTreeViewList callback → updates HomeScreen.treeViewList |
||
| 118 | ↓ |
||
| 119 | setState() triggers UI rebuild |
||
| 120 | 8. Tree Rendering Flow |
||
| 121 | TreeSliverThread (StatefulWidget) |
||
| 122 | Location: lib/ui/widgets/tree_sliver_thread.dart |
||
| 123 | State Update (_TreeSliverThreadState) |
||
| 124 | didUpdateWidget(TreeSliverThread oldWidget) |
||
| 125 | ↓ |
||
| 126 | IF treeViewList or threadMap changed: |
||
| 127 | ↓ |
||
| 128 | _updateTreeList() |
||
| 129 | ↓ |
||
| 130 | TreeSliverManager.buildFlatTreeList(threadMap, treeViewList) |
||
| 131 | → Returns: List<TreeNodeModel> _flatTreeList |
||
| 132 | ↓ |
||
| 133 | setState() → triggers rebuild |
||
| 134 | 9. Tree Structure Building |
||
| 135 | TreeSliverManager.buildFlatTreeList(...) |
||
| 136 | Location: lib/managers/tree_sliver_manager.dart |
||
| 137 | # This is the core algorithm that creates the tree structure shown in the UI |
||
| 138 | 1. _buildTreeStructure(treeViewList, threadMap) |
||
| 139 | → Returns: List<TreeNodeModel> treeNodes |
||
| 140 | |||
| 141 | Step A: Create all nodes (First Pass) |
||
| 142 | ───────────────────────────────────── |
||
| 143 | FOR EACH Point in treeViewList: |
||
| 144 | - Get first Exchange |
||
| 145 | - Check if Point has valid shards: |
||
| 146 | |||
| 147 | IF has valid shards: |
||
| 148 | → _createShardPointNode(point, exchange, nodeMap, threadMap) |
||
| 149 | Creates: |
||
| 150 | ├─ Main prompt node (no response) |
||
| 151 | ├─ Shard segment nodes (response parts) [stored in nodeMap] |
||
| 152 | └─ "After" segment if text remains |
||
| 153 | |||
| 154 | ELSE: |
||
| 155 | → Create regular TreeNodeModel |
||
| 156 | Contains both prompt and full response |
||
| 157 | |||
| 158 | Step B: Build hierarchy (Second Pass) |
||
| 159 | ────────────────────────────────────── |
||
| 160 | FOR EACH Point in treeViewList: |
||
| 161 | IF Point.parentPointId == Point.id: |
||
| 162 | → Add as root node |
||
| 163 | → IF has shards: Add shard segments as siblings |
||
| 164 | |||
| 165 | ELSE: |
||
| 166 | → Find parent (could be a shard segment) |
||
| 167 | → Add as child to parent |
||
| 168 | → Update level (parent.level + 1) |
||
| 169 | → IF this child has shards: Add child's shard segments as siblings |
||
| 170 | |||
| 171 | Step C: Update hasChildren flags |
||
| 172 | ────────────────────────────────── |
||
| 173 | |||
| 174 | 2. _flattenNode(node, flatList) [Recursive] |
||
| 175 | → Flattens tree based on expansion states |
||
| 176 | → Returns: List<TreeNodeModel> flatList |
||
| 177 | # Shard Point Node Creation (_createShardPointNode) |
||
| 178 | 1. Create main prompt node (no response content) |
||
| 179 | 2. Filter valid shards (validate anchor positions) |
||
| 180 | 3. Sort shards by start position |
||
| 181 | 4. FOR EACH shard: |
||
| 182 | - Extract response segment (currentPosition to shard.endPosition) |
||
| 183 | - Create TreeNodeModel for segment (NodeType.shardResponse) |
||
| 184 | - Store in nodeMap (NOT added as child of main node) |
||
| 185 | - FOR EACH child in shard.shardChildren: |
||
| 186 | → _buildChildNode(childPoint, shardSegment.id, shard.shardId, level=1, ...) |
||
| 187 | → Add child as child of shard segment |
||
| 188 | → IF child has shards: Add child's shard segments as siblings |
||
| 189 | - Update currentPosition |
||
| 190 | 5. Create "after" segment if text remains |
||
| 191 | 6. Return main prompt node |
||
| 192 | 10. Tree Item Rendering |
||
| 193 | TreeSliverThread.build(...) |
||
| 194 | SliverList.builder( |
||
| 195 | itemCount: _flatTreeList.length |
||
| 196 | itemBuilder: (context, index) |
||
| 197 | ↓ |
||
| 198 | Get TreeNodeModel node from _flatTreeList[index] |
||
| 199 | ↓ |
||
| 200 | Check if node.exchangeId is in loadingExchanges |
||
| 201 | ↓ |
||
| 202 | Return TreeSliverItem(node, isLoading, callbacks...) |
||
| 203 | ) |
||
| 204 | 11. Individual Tree Item Display |
||
| 205 | TreeSliverItem (StatelessWidget) |
||
| 206 | Location: lib/ui/widgets/tree_sliver_item.dart |
||
| 207 | # Build Method Structure |
||
| 208 | Container (with left margin based on node.level) |
||
| 209 | ↓ |
||
| 210 | Card |
||
| 211 | ↓ |
||
| 212 | InkWell (onTap: collapses/expands, selects node) |
||
| 213 | ↓ |
||
| 214 | Padding |
||
| 215 | ↓ |
||
| 216 | Column: |
||
| 217 | ├─ IF hasChildren: _buildExpansionHeader() |
||
| 218 | │ → Shows "Collapse" / "Expand" with icon |
||
| 219 | │ |
||
| 220 | ├─ IF promptContent != null: _buildPromptSection() |
||
| 221 | │ → Shows prompt with blue background |
||
| 222 | │ → Icon: person icon |
||
| 223 | │ → Label: "You" or "Shard" |
||
| 224 | │ |
||
| 225 | └─ IF responseContent != null: |
||
| 226 | IF isLoading: |
||
| 227 | → _buildStatusSection(StatusSectionConfig.loading()) |
||
| 228 | Shows "Waiting for response..." with spinner |
||
| 229 | ELSE: |
||
| 230 | → _buildResponseSection() |
||
| 231 | Shows response with color based on type: |
||
| 232 | - Green for AI Assistant |
||
| 233 | - Yellow for Shard Segments |
||
| 234 | # Content Section Rendering (_buildContentSection) |
||
| 235 | Container (colored background) |
||
| 236 | ↓ |
||
| 237 | Column: |
||
| 238 | ├─ Row (icon + label) |
||
| 239 | └─ _buildSelectableText(content) |
||
| 240 | ↓ |
||
| 241 | SelectableText with custom context menu |
||
| 242 | ↓ |
||
| 243 | IF text selected: |
||
| 244 | → Custom menu includes "Sub-prompt" option |
||
| 245 | → _handleSubPrompt(selectedText, selection) |
||
| 246 | ├─ Copy text to clipboard |
||
| 247 | ├─ Create PromptArgs with shard info |
||
| 248 | ├─ Call prepareSubPromptInput callback |
||
| 249 | └─ Updates HomeScreen state |
||
| 250 | 12. Sub-Prompt Preparation |
||
| 251 | HomeScreen.prepareSubPromptInput(PromptArgs promptArgs) |
||
| 252 | 1. Validate selectedText |
||
| 253 | 2. setState: |
||
| 254 | - Clear errorMessage |
||
| 255 | - Update this.promptArgs with new values: |
||
| 256 | * currentPointId |
||
| 257 | * isShardChild = true |
||
| 258 | * selectedText, startPosition, endPosition |
||
| 259 | 3. Show SnackBar: "Text copied to input..." |
||
| 260 | 4. Post frame callback: |
||
| 261 | - PromptInputFieldKey.currentState.setText(selectedText) |
||
| 262 | - PromptInputFieldKey.currentState.focusInput() |
||
| 263 | 13. Node Selection |
||
| 264 | HomeScreen.onNodeSelected(String currentPointId) |
||
| 265 | 1. setState: |
||
| 266 | - Update promptArgs with selected node's pointId |
||
| 267 | - Set isShardChild = false (continuing from this point) |
||
| 268 | 2. Focus input field for user to type next prompt |
||
| 269 | 14. Data Models |
||
| 270 | # Point Model (lib/models/point.dart) |
||
| 271 | Point: |
||
| 272 | - id: String |
||
| 273 | - parentPointId: String |
||
| 274 | - pointChildren: List<String> |
||
| 275 | - parentShardId: String? |
||
| 276 | - shardsList: List<Shard> |
||
| 277 | - exchangesList: List<Exchange> |
||
| 278 | - metadata: Metadata? |
||
| 279 | |||
| 280 | Shard: |
||
| 281 | - shardId: String |
||
| 282 | - shardChildren: List<String> |
||
| 283 | - anchor: Anchor (startPosition, endPosition, selectedText) |
||
| 284 | |||
| 285 | Exchange: |
||
| 286 | - exchangeId: String |
||
| 287 | - prompt: Prompt (model, promptMessage, maxTokens) |
||
| 288 | - response: Response? (with choices containing message content) |
||
| 289 | # TreeNodeModel (lib/models/tree_node_model.dart) |
||
| 290 | TreeNodeModel: |
||
| 291 | - id: String (unique identifier for display) |
||
| 292 | - pointId: String (actual Point.id) |
||
| 293 | - parentId: String? |
||
| 294 | - parentShardId: String? |
||
| 295 | - exchangeId: String? |
||
| 296 | - shardId: String? |
||
| 297 | - level: int (indentation level) |
||
| 298 | - isExpanded: bool |
||
| 299 | - hasChildren: bool |
||
| 300 | - promptContent: String? |
||
| 301 | - responseContent: String? |
||
| 302 | - promptRole: String? |
||
| 303 | - responseRole: String? |
||
| 304 | - nodeType: NodeType (exchange, shard, shardResponse) |
||
| 305 | - children: List<TreeNodeModel> |
||
| 306 | - shardStartPosition: int? |
||
| 307 | - shardEndPosition: int? |
||
| 308 | - choiceIndex: int? |
||
| 309 | 15. Complete Flow Summary for Screenshot Example |
||
| 310 | Scenario: "What's Earth?" conversation |
||
| 311 | 1. User types "What's Earth?" → HomeScreen.onPrompt() |
||
| 312 | ↓ |
||
| 313 | 2. HomeScreenManager.handlePrompt() |
||
| 314 | ↓ |
||
| 315 | 3. ThreadManager.updateThreadMap() → Creates Point T001 |
||
| 316 | ↓ |
||
| 317 | 4. ApiService.sendPromptToAi() → Returns response "Third planet from the Sun" |
||
| 318 | ↓ |
||
| 319 | 5. TreeViewManager.updateTreeViewList() → [T001] |
||
| 320 | ↓ |
||
| 321 | 6. TreeSliverManager.buildFlatTreeList() |
||
| 322 | → Creates TreeNodeModel for T001 (prompt + response) |
||
| 323 | ↓ |
||
| 324 | 7. TreeSliverThread renders TreeSliverItem |
||
| 325 | → Shows blue "You" box: "What's Earth?" |
||
| 326 | → Shows green "AI Assistant" box: "Third planet from the Sun" |
||
| 327 | |||
| 328 | 8. User selects "Sun" text → Right-click → "Sub-prompt" |
||
| 329 | ↓ |
||
| 330 | 9. HomeScreen.prepareSubPromptInput() |
||
| 331 | → Sets promptArgs with shard info |
||
| 332 | → Fills input field with "Sun - what's this?" |
||
| 333 | ↓ |
||
| 334 | 10. User types sub-prompt → HomeScreen.onPrompt() |
||
| 335 | ↓ |
||
| 336 | 11. ThreadManager.updateThreadMap() with isShardChild=true |
||
| 337 | ↓ |
||
| 338 | 12. ShardManager.addShardToParentPoint() |
||
| 339 | → Creates Shard S01 in Point T001 |
||
| 340 | → Creates new Point T002 (parentShardId = S01) |
||
| 341 | ↓ |
||
| 342 | 13. TreeSliverManager rebuilds tree: |
||
| 343 | Point T001 now split into: |
||
| 344 | ├─ Prompt: "What's Earth?" |
||
| 345 | ├─ Response segment: "Third planet from the " |
||
| 346 | ├─ Shard segment (with children): |
||
| 347 | │ ├─ "Sun" |
||
| 348 | │ └─ Child: T002 |
||
| 349 | │ ├─ Prompt: "Sun - what's this?" |
||
| 350 | │ └─ Response: "The star" + "A luminous sphere" + "of plasma..." |
||
| 351 | └─ After segment: " [remaining text]" |
||
| 352 | |||
| 353 | 14. Each subsequent sub-prompt repeats steps 8-13, |
||
| 354 | creating nested tree structure as shown in screenshot |