package dify import ( "bufio" "context" "encoding/json" "fmt" "io" "strings" ) // ChatStream sends a chat message and returns a reader for SSE events. // Caller is responsible for closing the returned io.ReadCloser. func (c *Client) ChatStream(ctx context.Context, apiKey string, req *ChatRequest) (io.ReadCloser, error) { req.ResponseMode = "streaming" resp, err := c.do(ctx, "POST", "/chat-messages", apiKey, req) if err != nil { return nil, err } return resp.Body, nil } // ChatBlocking sends a chat message and waits for the complete response. func (c *Client) ChatBlocking(ctx context.Context, apiKey string, req *ChatRequest) (*ChatStreamEvent, error) { req.ResponseMode = "blocking" var result ChatStreamEvent if err := c.doJSON(ctx, "POST", "/chat-messages", apiKey, req, &result); err != nil { return nil, err } return &result, nil } // ParseSSEStream parses a Dify SSE stream and calls handler for each event. func ParseSSEStream(reader io.Reader, handler func(event ChatStreamEvent) error) error { scanner := bufio.NewScanner(reader) scanner.Buffer(make([]byte, 64*1024), 256*1024) for scanner.Scan() { line := scanner.Text() if !strings.HasPrefix(line, "data: ") { continue } data := strings.TrimPrefix(line, "data: ") if data == "[DONE]" { break } var event ChatStreamEvent if err := json.Unmarshal([]byte(data), &event); err != nil { continue } if err := handler(event); err != nil { return err } } return scanner.Err() } // ListConversations returns the user's conversation list for an app. func (c *Client) ListConversations(ctx context.Context, apiKey, user string, limit int, firstID string) (*ConversationListResponse, error) { path := fmt.Sprintf("/conversations?user=%s&limit=%d", user, limit) if firstID != "" { path += "&first_id=" + firstID } var result ConversationListResponse if err := c.doJSON(ctx, "GET", path, apiKey, nil, &result); err != nil { return nil, err } return &result, nil } // ListMessages returns messages in a conversation. func (c *Client) ListMessages(ctx context.Context, apiKey, user, conversationID string, limit int, firstID string) (*MessageListResponse, error) { path := fmt.Sprintf("/messages?user=%s&conversation_id=%s&limit=%d", user, conversationID, limit) if firstID != "" { path += "&first_id=" + firstID } var result MessageListResponse if err := c.doJSON(ctx, "GET", path, apiKey, nil, &result); err != nil { return nil, err } return &result, nil } // DeleteConversation deletes a conversation. func (c *Client) DeleteConversation(ctx context.Context, apiKey, user, conversationID string) error { body := map[string]string{"user": user} return c.doJSON(ctx, "DELETE", "/conversations/"+conversationID, apiKey, body, nil) } // SubmitFeedback submits feedback for a message. func (c *Client) SubmitFeedback(ctx context.Context, apiKey, messageID string, req *FeedbackRequest) error { return c.doJSON(ctx, "POST", "/messages/"+messageID+"/feedbacks", apiKey, req, nil) }