| 1 |
/** |
| 2 |
* Deterministic instance ID resolver for inline components. |
| 3 |
* |
| 4 |
* The same componentType + props must produce the same instanceId in three |
| 5 |
* render paths (live SSE, persisted history, pending_component re-injection) |
| 6 |
* so the per-instance state in `useComponentStateStore` (resolved / |
| 7 |
* superseded / submitted values) survives reload. |
| 8 |
* |
| 9 |
* Priority: explicit `instance_id` > `call_id` (per-tool-call unique) > |
| 10 |
* `form_id` > derived key + props hash. call_id beats form_id because the |
| 11 |
* brain reuses form_id values across distinct tool calls (e.g. two smart- |
| 12 |
* forms with form_id "website-batch-1" in different turns), which would |
| 13 |
* otherwise collide on submitted state. |
| 14 |
*/ |
| 15 |
|
| 16 |
function stablePropsHash(props) { |
| 17 |
try { |
| 18 |
const str = JSON.stringify(props); |
| 19 |
let hash = 5381; |
| 20 |
for (let i = 0; i < str.length; i += 1) { |
| 21 |
hash = ((hash << 5) + hash) + str.charCodeAt(i); |
| 22 |
hash |= 0; // keep 32-bit |
| 23 |
} |
| 24 |
return Math.abs(hash).toString(36); |
| 25 |
} catch { |
| 26 |
return '0'; |
| 27 |
} |
| 28 |
} |
| 29 |
|
| 30 |
export function resolveComponentInstanceId(componentType, props = {}) { |
| 31 |
if (props.instance_id) return props.instance_id; |
| 32 |
if (props.call_id) return props.call_id; |
| 33 |
if (props.form_id) return `${props.form_id}-${stablePropsHash(props)}`; |
| 34 |
const derivedKey = props.brief?.business_name || props.title || props.action || 'default'; |
| 35 |
return `${componentType}-${derivedKey}-${stablePropsHash(props)}`; |
| 36 |
} |
| 37 |
|