Commit 6ef729c8 authored by jan.koester's avatar jan.koester
Browse files

reasoning

parent 9b21977b
Loading
Loading
Loading
Loading
+16 −0
Changes for editor/html/css/editor.css: 16 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -807,6 +807,22 @@ dialog.ai-panel::backdrop {
    font-style: italic;
    margin-top: 4px;
}
.ai-msg-thinking {
    color: var(--text-secondary);
    font-size: 12px;
    font-style: italic;
    margin-bottom: 6px;
    border-left: 2px solid var(--border);
    padding-left: 8px;
}
.ai-msg-thinking summary {
    cursor: pointer;
    font-style: normal;
}
.ai-msg-thinking > div {
    margin-top: 4px;
    white-space: pre-wrap;
}
.ai-msg.assistant .ai-msg-actions {
    display: flex; gap: 6px; margin-top: 6px;
}
+2 −2
Changes for editor/html/js/api.js: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -343,9 +343,9 @@ var EditorApi = (function() {
                if (contentType.indexOf('text/event-stream') === -1) {
                    // Non-streaming fallback (HTTP/2 or HTTP/3 connection).
                    return response.json().then(function(data) {
                        var done = { type: 'done', result: data.result, applied: data.applied, tool_calls: data.tool_calls };
                        var done = { type: 'done', result: data.result, thinking: data.thinking, applied: data.applied, tool_calls: data.tool_calls };
                        if (onEvent) onEvent(done);
                        return { result: data.result, applied: data.applied, tool_calls: data.tool_calls };
                        return { result: data.result, thinking: data.thinking, applied: data.applied, tool_calls: data.tool_calls };
                    });
                }

+24 −0
Changes for editor/html/js/editor.js: 24 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -1651,11 +1651,14 @@

                if (ev.type === 'content_delta') {
                    streamMsg.appendText(ev.text || '');
                } else if (ev.type === 'thinking_delta') {
                    streamMsg.appendThinking(ev.text || '');
                } else if (ev.type === 'tool_call') {
                    streamMsg.setStatus(I18n.t('I18N_AI_CALLING_TOOL', 'Werkzeug wird aufgerufen: ') + (ev.name || ''));
                } else if (ev.type === 'error') {
                    addMessage('error', I18n.t('I18N_AI_FAILED', 'KI-Anfrage fehlgeschlagen') + ': ' + (ev.message || ''));
                } else if (ev.type === 'done') {
                    if (ev.thinking) streamMsg.appendThinking(ev.thinking);
                    if (ev.applied) {
                        Canvas.clearSelection(); DocumentTree.clearSelection();
                        PropertiesPanel.clear();
@@ -1710,6 +1713,8 @@
            var div = document.createElement('div');
            div.className = 'ai-msg assistant';

            var thinkingDetails = null, thinkingBody = null;

            var content = document.createElement('div');
            div.appendChild(content);

@@ -1724,7 +1729,25 @@
            var fullText = '';

            return {
                appendThinking: function(piece) {
                    if (!thinkingDetails) {
                        thinkingDetails = document.createElement('details');
                        thinkingDetails.className = 'ai-msg-thinking';
                        thinkingDetails.open = true;
                        var summary = document.createElement('summary');
                        summary.textContent = I18n.t('I18N_AI_THINKING', 'Gedanken');
                        thinkingDetails.appendChild(summary);
                        thinkingBody = document.createElement('div');
                        thinkingDetails.appendChild(thinkingBody);
                        div.insertBefore(thinkingDetails, content);
                    }
                    thinkingBody.textContent += piece;
                    chatHistory.scrollTop = chatHistory.scrollHeight;
                },
                appendText: function(piece) {
                    // The model has moved from thinking to answering -- collapse
                    // the thoughts so the answer is what's visible by default.
                    if (thinkingDetails && thinkingDetails.open) thinkingDetails.open = false;
                    fullText += piece;
                    content.textContent = fullText;
                    statusLine.style.display = 'none';
@@ -1736,6 +1759,7 @@
                    chatHistory.scrollTop = chatHistory.scrollHeight;
                },
                finalize: function(applied, toolCalls) {
                    if (thinkingDetails) thinkingDetails.open = false;
                    if (applied) {
                        statusLine.textContent = '✓ ' + toolCalls + ' ' +
                            I18n.t('I18N_AI_APPLIED', 'Änderung(en) direkt am Dokument angewendet');
+26 −0
Changes for editor/src/webedit_api.cpp: 26 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -3757,6 +3757,7 @@ void webedit::Api::handleAiPrompt(libhttppp::HttpRequest &curreq,

    if (!canStream) {
        std::string finalResult;
        std::string finalThinking;
        bool anyMutation = false;
        int toolCallCount = 0;
        bool haveFinal = false;
@@ -3883,6 +3884,10 @@ void webedit::Api::handleAiPrompt(libhttppp::HttpRequest &curreq,
                json_object *content = nullptr;
                if (json_object_object_get_ex(message, "content", &content) && content)
                    finalResult = json_object_get_string(content);
                json_object *reasoning = nullptr;
                if ((json_object_object_get_ex(message, "reasoning_content", &reasoning) && reasoning) ||
                    (json_object_object_get_ex(message, "reasoning", &reasoning) && reasoning))
                    finalThinking = json_object_get_string(reasoning);
                json_object_put(aiResp);
                haveFinal = true;
            }
@@ -3901,6 +3906,8 @@ void webedit::Api::handleAiPrompt(libhttppp::HttpRequest &curreq,

        json_object *resp = json_object_new_object();
        json_object_object_add(resp, "result", json_object_new_string(finalResult.c_str()));
        if (!finalThinking.empty())
            json_object_object_add(resp, "thinking", json_object_new_string(finalThinking.c_str()));
        json_object_object_add(resp, "applied", json_object_new_boolean(anyMutation));
        json_object_object_add(resp, "tool_calls", json_object_new_int(toolCallCount));
        sendJson(curreq, resp);
@@ -4015,6 +4022,25 @@ void webedit::Api::handleAiPrompt(libhttppp::HttpRequest &curreq,
                                }
                            }

                            // Reasoning models (DeepSeek-R1, QwQ, etc. served behind an
                            // OpenAI-compatible endpoint) stream their chain-of-thought
                            // separately from the answer, as "reasoning_content" (or
                            // "reasoning" on some backends) -- surface it as its own
                            // event so the UI can show it distinctly instead of dropping
                            // it silently.
                            json_object *jReasoning = nullptr;
                            if (!json_object_object_get_ex(delta, "reasoning_content", &jReasoning) || !jReasoning)
                                json_object_object_get_ex(delta, "reasoning", &jReasoning);
                            if (jReasoning && json_object_get_type(jReasoning) == json_type_string) {
                                std::string piece = json_object_get_string(jReasoning);
                                if (!piece.empty()) {
                                    json_object *ev = json_object_new_object();
                                    json_object_object_add(ev, "type", json_object_new_string("thinking_delta"));
                                    json_object_object_add(ev, "text", json_object_new_string(piece.c_str()));
                                    pushEvent(ev);
                                }
                            }

                            json_object *jToolCalls = nullptr;
                            if (json_object_object_get_ex(delta, "tool_calls", &jToolCalls) && jToolCalls &&
                                json_object_get_type(jToolCalls) == json_type_array) {
+2 −0
Changes for src/translations.h: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -365,6 +365,7 @@ namespace blogi {
            {"I18N_AI_MODEL",            "Modell"},
            {"I18N_AI_MAX_TOKENS",       "Max Tokens"},
            {"I18N_AI_MAX_TOOL_ROUNDS",  "Max Werkzeug-Runden"},
            {"I18N_AI_THINKING",         "Gedanken"},
            {"I18N_AI_CONTEXT",          "Kontext"},
            {"I18N_AI_GENERATE",         "Text generieren"},
            {"I18N_AI_REWRITE",          "Element umschreiben"},
@@ -867,6 +868,7 @@ namespace blogi {
            {"I18N_AI_MODEL",            "Model"},
            {"I18N_AI_MAX_TOKENS",       "Max Tokens"},
            {"I18N_AI_MAX_TOOL_ROUNDS",  "Max tool rounds"},
            {"I18N_AI_THINKING",         "Thoughts"},
            {"I18N_AI_CONTEXT",          "Context"},
            {"I18N_AI_GENERATE",         "Generate text"},
            {"I18N_AI_REWRITE",          "Rewrite element"},