@emblemvault/hustle-react 1.3.0 → 1.4.1

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.
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { createContext, useState, useEffect, useCallback, useRef, useMemo, useContext } from 'react';
2
3
  import { HustleIncognitoClient } from 'hustle-incognito';
3
4
  import { useEmblemAuthOptional } from '@emblemvault/emblem-auth-react';
@@ -3547,7 +3548,7 @@ var screenshotExecutor = async (args2) => {
3547
3548
  if (!window.html2canvas) {
3548
3549
  await new Promise((resolve, reject) => {
3549
3550
  const script = document.createElement("script");
3550
- script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
3551
+ script.src = "https://cdn.jsdelivr.net/npm/html2canvas-pro@1.5.0/dist/html2canvas-pro.min.js";
3551
3552
  script.onload = () => resolve();
3552
3553
  script.onerror = () => reject(new Error("Failed to load html2canvas"));
3553
3554
  document.head.appendChild(script);
@@ -14870,7 +14871,11 @@ var styles = {
14870
14871
  border: "none",
14871
14872
  borderRadius: 0,
14872
14873
  color: tokens.colors.textTertiary,
14873
- flexShrink: 0
14874
+ flexShrink: 0,
14875
+ display: "flex",
14876
+ alignItems: "center",
14877
+ justifyContent: "center",
14878
+ cursor: "pointer"
14874
14879
  },
14875
14880
  inputWrapper: {
14876
14881
  flex: 1
@@ -15092,6 +15097,7 @@ function HustleChat({
15092
15097
  isLoading,
15093
15098
  error: error2,
15094
15099
  models,
15100
+ client,
15095
15101
  chatStream,
15096
15102
  uploadFile,
15097
15103
  selectedModel,
@@ -15118,6 +15124,8 @@ function HustleChat({
15118
15124
  const [showSettingsPanel, setShowSettingsPanel] = useState(false);
15119
15125
  const messagesEndRef = useRef(null);
15120
15126
  const fileInputRef = useRef(null);
15127
+ const messagesRef = useRef(messages);
15128
+ const pendingAutoContinueRef = useRef(false);
15121
15129
  useEffect(() => {
15122
15130
  if (initialSystemPrompt && !systemPrompt) {
15123
15131
  setSystemPrompt(initialSystemPrompt);
@@ -15126,6 +15134,9 @@ function HustleChat({
15126
15134
  useEffect(() => {
15127
15135
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
15128
15136
  }, [messages]);
15137
+ useEffect(() => {
15138
+ messagesRef.current = messages;
15139
+ }, [messages]);
15129
15140
  const handleFileSelect = useCallback(
15130
15141
  async (e) => {
15131
15142
  const files = e.target.files;
@@ -15147,6 +15158,101 @@ function HustleChat({
15147
15158
  const removeAttachment = useCallback((index) => {
15148
15159
  setAttachments((prev) => prev.filter((_2, i) => i !== index));
15149
15160
  }, []);
15161
+ const sendContinue = useCallback(async () => {
15162
+ if (isStreaming || !isReady) return;
15163
+ console.log("[AUTO_CONTINUE] Sending continue message...");
15164
+ const userMessage = {
15165
+ id: generateId(),
15166
+ role: "user",
15167
+ content: "continue"
15168
+ };
15169
+ setMessages((prev) => [...prev, userMessage]);
15170
+ onMessage?.(userMessage);
15171
+ const assistantMessage = {
15172
+ id: generateId(),
15173
+ role: "assistant",
15174
+ content: "",
15175
+ isStreaming: true,
15176
+ toolCalls: []
15177
+ };
15178
+ setMessages((prev) => [...prev, assistantMessage]);
15179
+ setIsStreaming(true);
15180
+ setCurrentToolCalls([]);
15181
+ try {
15182
+ const chatMessages = messagesRef.current.filter((m2) => !m2.isStreaming).map((m2) => ({ role: m2.role, content: m2.content }));
15183
+ chatMessages.push({ role: "user", content: "continue" });
15184
+ const stream = chatStream({
15185
+ messages: chatMessages,
15186
+ processChunks: true
15187
+ });
15188
+ let fullContent = "";
15189
+ const toolCallsAccumulated = [];
15190
+ for await (const chunk of stream) {
15191
+ if (chunk.type === "text") {
15192
+ fullContent += chunk.value;
15193
+ setMessages(
15194
+ (prev) => prev.map(
15195
+ (m2) => m2.id === assistantMessage.id ? { ...m2, content: fullContent } : m2
15196
+ )
15197
+ );
15198
+ } else if (chunk.type === "tool_call") {
15199
+ const toolCall = chunk.value;
15200
+ toolCallsAccumulated.push(toolCall);
15201
+ setCurrentToolCalls([...toolCallsAccumulated]);
15202
+ setMessages(
15203
+ (prev) => prev.map(
15204
+ (m2) => m2.id === assistantMessage.id ? { ...m2, toolCalls: [...toolCallsAccumulated] } : m2
15205
+ )
15206
+ );
15207
+ onToolCall?.(toolCall);
15208
+ } else if (chunk.type === "error") {
15209
+ console.error("Stream error:", chunk.value);
15210
+ }
15211
+ }
15212
+ const processedResponse = await stream.response;
15213
+ const finalContent = processedResponse?.content || fullContent || "(No response)";
15214
+ setMessages(
15215
+ (prev) => prev.map(
15216
+ (m2) => m2.id === assistantMessage.id ? { ...m2, isStreaming: false, content: finalContent } : m2
15217
+ )
15218
+ );
15219
+ onResponse?.(finalContent);
15220
+ } catch (err) {
15221
+ console.error("Continue error:", err);
15222
+ setMessages(
15223
+ (prev) => prev.map(
15224
+ (m2) => m2.id === assistantMessage.id ? { ...m2, isStreaming: false, content: `Error: ${err instanceof Error ? err.message : "Unknown error"}` } : m2
15225
+ )
15226
+ );
15227
+ } finally {
15228
+ setIsStreaming(false);
15229
+ setCurrentToolCalls([]);
15230
+ }
15231
+ }, [isStreaming, isReady, chatStream, onMessage, onToolCall, onResponse]);
15232
+ useEffect(() => {
15233
+ if (!client) return;
15234
+ const unsubMaxTools = client.on("max_tools_reached", (event) => {
15235
+ console.log(`[AUTO_CONTINUE] Max tools reached (${event.toolsExecuted}/${event.maxSteps}), queuing auto-continue...`);
15236
+ pendingAutoContinueRef.current = true;
15237
+ });
15238
+ const unsubTimeout = client.on("timeout", (event) => {
15239
+ console.log(`[AUTO_CONTINUE] Timeout: ${event.message}, queuing auto-continue...`);
15240
+ pendingAutoContinueRef.current = true;
15241
+ });
15242
+ return () => {
15243
+ unsubMaxTools();
15244
+ unsubTimeout();
15245
+ };
15246
+ }, [client]);
15247
+ useEffect(() => {
15248
+ if (!isStreaming && pendingAutoContinueRef.current && isReady) {
15249
+ console.log("[AUTO_CONTINUE] Streaming ended, triggering continue...");
15250
+ pendingAutoContinueRef.current = false;
15251
+ setTimeout(() => {
15252
+ sendContinue();
15253
+ }, 50);
15254
+ }
15255
+ }, [isStreaming, isReady, sendContinue]);
15150
15256
  const sendMessage = useCallback(async () => {
15151
15257
  const content = inputValue.trim();
15152
15258
  if (!content || isStreaming || !isReady) return;