@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.
package/dist/index.js CHANGED
@@ -2006,7 +2006,7 @@ var screenshotExecutor = async (args2) => {
2006
2006
  if (!window.html2canvas) {
2007
2007
  await new Promise((resolve, reject) => {
2008
2008
  const script = document.createElement("script");
2009
- script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
2009
+ script.src = "https://cdn.jsdelivr.net/npm/html2canvas-pro@1.5.0/dist/html2canvas-pro.min.js";
2010
2010
  script.onload = () => resolve();
2011
2011
  script.onerror = () => reject(new Error("Failed to load html2canvas"));
2012
2012
  document.head.appendChild(script);
@@ -3715,7 +3715,11 @@ var styles = {
3715
3715
  border: "none",
3716
3716
  borderRadius: 0,
3717
3717
  color: tokens.colors.textTertiary,
3718
- flexShrink: 0
3718
+ flexShrink: 0,
3719
+ display: "flex",
3720
+ alignItems: "center",
3721
+ justifyContent: "center",
3722
+ cursor: "pointer"
3719
3723
  },
3720
3724
  inputWrapper: {
3721
3725
  flex: 1
@@ -3937,6 +3941,7 @@ function HustleChat({
3937
3941
  isLoading,
3938
3942
  error: error2,
3939
3943
  models,
3944
+ client,
3940
3945
  chatStream,
3941
3946
  uploadFile,
3942
3947
  selectedModel,
@@ -3963,6 +3968,8 @@ function HustleChat({
3963
3968
  const [showSettingsPanel, setShowSettingsPanel] = useState(false);
3964
3969
  const messagesEndRef = useRef(null);
3965
3970
  const fileInputRef = useRef(null);
3971
+ const messagesRef = useRef(messages);
3972
+ const pendingAutoContinueRef = useRef(false);
3966
3973
  useEffect(() => {
3967
3974
  if (initialSystemPrompt && !systemPrompt) {
3968
3975
  setSystemPrompt(initialSystemPrompt);
@@ -3971,6 +3978,9 @@ function HustleChat({
3971
3978
  useEffect(() => {
3972
3979
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
3973
3980
  }, [messages]);
3981
+ useEffect(() => {
3982
+ messagesRef.current = messages;
3983
+ }, [messages]);
3974
3984
  const handleFileSelect = useCallback(
3975
3985
  async (e) => {
3976
3986
  const files = e.target.files;
@@ -3992,6 +4002,101 @@ function HustleChat({
3992
4002
  const removeAttachment = useCallback((index) => {
3993
4003
  setAttachments((prev) => prev.filter((_, i) => i !== index));
3994
4004
  }, []);
4005
+ const sendContinue = useCallback(async () => {
4006
+ if (isStreaming || !isReady) return;
4007
+ console.log("[AUTO_CONTINUE] Sending continue message...");
4008
+ const userMessage = {
4009
+ id: generateId(),
4010
+ role: "user",
4011
+ content: "continue"
4012
+ };
4013
+ setMessages((prev) => [...prev, userMessage]);
4014
+ onMessage?.(userMessage);
4015
+ const assistantMessage = {
4016
+ id: generateId(),
4017
+ role: "assistant",
4018
+ content: "",
4019
+ isStreaming: true,
4020
+ toolCalls: []
4021
+ };
4022
+ setMessages((prev) => [...prev, assistantMessage]);
4023
+ setIsStreaming(true);
4024
+ setCurrentToolCalls([]);
4025
+ try {
4026
+ const chatMessages = messagesRef.current.filter((m) => !m.isStreaming).map((m) => ({ role: m.role, content: m.content }));
4027
+ chatMessages.push({ role: "user", content: "continue" });
4028
+ const stream = chatStream({
4029
+ messages: chatMessages,
4030
+ processChunks: true
4031
+ });
4032
+ let fullContent = "";
4033
+ const toolCallsAccumulated = [];
4034
+ for await (const chunk of stream) {
4035
+ if (chunk.type === "text") {
4036
+ fullContent += chunk.value;
4037
+ setMessages(
4038
+ (prev) => prev.map(
4039
+ (m) => m.id === assistantMessage.id ? { ...m, content: fullContent } : m
4040
+ )
4041
+ );
4042
+ } else if (chunk.type === "tool_call") {
4043
+ const toolCall = chunk.value;
4044
+ toolCallsAccumulated.push(toolCall);
4045
+ setCurrentToolCalls([...toolCallsAccumulated]);
4046
+ setMessages(
4047
+ (prev) => prev.map(
4048
+ (m) => m.id === assistantMessage.id ? { ...m, toolCalls: [...toolCallsAccumulated] } : m
4049
+ )
4050
+ );
4051
+ onToolCall?.(toolCall);
4052
+ } else if (chunk.type === "error") {
4053
+ console.error("Stream error:", chunk.value);
4054
+ }
4055
+ }
4056
+ const processedResponse = await stream.response;
4057
+ const finalContent = processedResponse?.content || fullContent || "(No response)";
4058
+ setMessages(
4059
+ (prev) => prev.map(
4060
+ (m) => m.id === assistantMessage.id ? { ...m, isStreaming: false, content: finalContent } : m
4061
+ )
4062
+ );
4063
+ onResponse?.(finalContent);
4064
+ } catch (err) {
4065
+ console.error("Continue error:", err);
4066
+ setMessages(
4067
+ (prev) => prev.map(
4068
+ (m) => m.id === assistantMessage.id ? { ...m, isStreaming: false, content: `Error: ${err instanceof Error ? err.message : "Unknown error"}` } : m
4069
+ )
4070
+ );
4071
+ } finally {
4072
+ setIsStreaming(false);
4073
+ setCurrentToolCalls([]);
4074
+ }
4075
+ }, [isStreaming, isReady, chatStream, onMessage, onToolCall, onResponse]);
4076
+ useEffect(() => {
4077
+ if (!client) return;
4078
+ const unsubMaxTools = client.on("max_tools_reached", (event) => {
4079
+ console.log(`[AUTO_CONTINUE] Max tools reached (${event.toolsExecuted}/${event.maxSteps}), queuing auto-continue...`);
4080
+ pendingAutoContinueRef.current = true;
4081
+ });
4082
+ const unsubTimeout = client.on("timeout", (event) => {
4083
+ console.log(`[AUTO_CONTINUE] Timeout: ${event.message}, queuing auto-continue...`);
4084
+ pendingAutoContinueRef.current = true;
4085
+ });
4086
+ return () => {
4087
+ unsubMaxTools();
4088
+ unsubTimeout();
4089
+ };
4090
+ }, [client]);
4091
+ useEffect(() => {
4092
+ if (!isStreaming && pendingAutoContinueRef.current && isReady) {
4093
+ console.log("[AUTO_CONTINUE] Streaming ended, triggering continue...");
4094
+ pendingAutoContinueRef.current = false;
4095
+ setTimeout(() => {
4096
+ sendContinue();
4097
+ }, 50);
4098
+ }
4099
+ }, [isStreaming, isReady, sendContinue]);
3995
4100
  const sendMessage = useCallback(async () => {
3996
4101
  const content = inputValue.trim();
3997
4102
  if (!content || isStreaming || !isReady) return;