mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-25 06:02:57 +08:00
## 🔍 Overview This update adds a credit system under the hood which will be used for our CDCK Hosted LLM models so we can make our features more accessible to our hosted customers! ## 📷 Screenshots <img width="1105" height="268" alt="Screenshot 2025-10-02 at 12 48 58" src="https://github.com/user-attachments/assets/2a07d89b-7510-4565-82bb-26b46fbcf5c4" /> _☝🏽 ` ProblemCheck` notices to inform customers_ <img width="1077" height="472" alt="Screenshot 2025-10-02 at 12 49 41" src="https://github.com/user-attachments/assets/b72028f7-5df2-45a8-8c71-65cf750755ab" /> _☝🏽 AI Usage page for easy monitoring_ <img width="1112" height="1083" alt="Screenshot 2025-10-02 at 18 17 01" src="https://github.com/user-attachments/assets/a01992d5-15a0-472a-9501-bc3bc9a54ade" /> _☝🏽 Credit bars underneath relevant LLM models_ <img width="866" height="267" alt="Screenshot 2025-10-03 at 11 35 19" src="https://github.com/user-attachments/assets/e7b4c0e7-c93d-4b0f-923d-79ac5d53028b" /> _☝🏽 Dialog box when trying to use without available credits_
162 lines
4.3 KiB
JavaScript
Vendored
162 lines
4.3 KiB
JavaScript
Vendored
import { getOwner } from "@ember/owner";
|
|
import { setupTest } from "ember-qunit";
|
|
import { module, test } from "qunit";
|
|
import sinon from "sinon";
|
|
import {
|
|
isAiCreditLimitError,
|
|
popupAiCreditLimitError,
|
|
} from "discourse/plugins/discourse-ai/discourse/lib/ai-errors";
|
|
|
|
module("Unit | Utility | ai-errors", function (hooks) {
|
|
setupTest(hooks);
|
|
|
|
module("isAiCreditLimitError", function () {
|
|
test("detects AJAX error format from controller", function (assert) {
|
|
const error = {
|
|
jqXHR: {
|
|
responseJSON: {
|
|
error: "credit_limit_exceeded",
|
|
},
|
|
},
|
|
};
|
|
|
|
assert.true(
|
|
isAiCreditLimitError(error),
|
|
"Should detect controller error format"
|
|
);
|
|
});
|
|
|
|
test("detects MessageBus payload format from streaming job", function (assert) {
|
|
const payload = {
|
|
error_type: "credit_limit_exceeded",
|
|
message: "Credit limit exceeded",
|
|
details: {},
|
|
};
|
|
|
|
assert.true(
|
|
isAiCreditLimitError(payload),
|
|
"Should detect streaming job format"
|
|
);
|
|
});
|
|
|
|
test("detects direct error object format", function (assert) {
|
|
const error = {
|
|
error: "credit_limit_exceeded",
|
|
};
|
|
|
|
assert.true(
|
|
isAiCreditLimitError(error),
|
|
"Should detect direct error format"
|
|
);
|
|
});
|
|
|
|
test("returns false for non-credit-limit errors", function (assert) {
|
|
const error = {
|
|
jqXHR: {
|
|
responseJSON: {
|
|
error: "some_other_error",
|
|
},
|
|
},
|
|
};
|
|
|
|
assert.false(
|
|
isAiCreditLimitError(error),
|
|
"Should not detect other errors"
|
|
);
|
|
});
|
|
|
|
test("returns false for unrelated objects", function (assert) {
|
|
assert.false(
|
|
isAiCreditLimitError({}),
|
|
"Should return false for empty object"
|
|
);
|
|
assert.false(isAiCreditLimitError(null), "Should return false for null");
|
|
assert.false(
|
|
isAiCreditLimitError(undefined),
|
|
"Should return false for undefined"
|
|
);
|
|
});
|
|
});
|
|
|
|
module("popupAiCreditLimitError", function () {
|
|
test("shows dialog with reset time when available", function (assert) {
|
|
const dialogService = getOwner(this).lookup("service:dialog");
|
|
const alertStub = sinon.stub(dialogService, "alert");
|
|
|
|
const error = {
|
|
jqXHR: {
|
|
responseJSON: {
|
|
error: "credit_limit_exceeded",
|
|
details: {
|
|
reset_time_absolute: "5:40pm on Dec 25, 2024",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
popupAiCreditLimitError(error);
|
|
|
|
assert.true(alertStub.calledOnce, "Dialog should be shown");
|
|
const callArgs = alertStub.firstCall.args[0];
|
|
assert.true(
|
|
callArgs.message.includes("5:40pm on Dec 25, 2024"),
|
|
"Message should include reset time"
|
|
);
|
|
assert.strictEqual(
|
|
callArgs.title,
|
|
"AI credit limit reached",
|
|
"Title should be correct"
|
|
);
|
|
|
|
alertStub.restore();
|
|
});
|
|
|
|
test("shows dialog without reset time when unavailable", function (assert) {
|
|
const dialogService = getOwner(this).lookup("service:dialog");
|
|
const alertStub = sinon.stub(dialogService, "alert");
|
|
|
|
const error = {
|
|
error_type: "credit_limit_exceeded",
|
|
details: {},
|
|
};
|
|
|
|
popupAiCreditLimitError(error);
|
|
|
|
assert.true(alertStub.calledOnce, "Dialog should be shown");
|
|
const callArgs = alertStub.firstCall.args[0];
|
|
assert.false(
|
|
callArgs.message.includes("at"),
|
|
"Message should not include 'at' for time"
|
|
);
|
|
assert.true(
|
|
callArgs.message.includes("until your limit resets"),
|
|
"Message should mention reset"
|
|
);
|
|
|
|
alertStub.restore();
|
|
});
|
|
|
|
test("handles MessageBus payload format", function (assert) {
|
|
const dialogService = getOwner(this).lookup("service:dialog");
|
|
const alertStub = sinon.stub(dialogService, "alert");
|
|
|
|
const payload = {
|
|
error_type: "credit_limit_exceeded",
|
|
details: {
|
|
reset_time_relative: "in 2 hours",
|
|
},
|
|
};
|
|
|
|
popupAiCreditLimitError(payload);
|
|
|
|
assert.true(alertStub.calledOnce, "Dialog should be shown");
|
|
const callArgs = alertStub.firstCall.args[0];
|
|
assert.true(
|
|
callArgs.message.includes("in 2 hours"),
|
|
"Message should include relative time"
|
|
);
|
|
|
|
alertStub.restore();
|
|
});
|
|
});
|
|
});
|