Merge branch 'main' into feat-knowlege-ocr
This commit is contained in:
@@ -1,85 +0,0 @@
|
||||
diff --git a/core.js b/core.js
|
||||
index 862d66101f441fb4f47dfc8cff5e2d39e1f5a11e..6464bebbf696c39d35f0368f061ea4236225c162 100644
|
||||
--- a/core.js
|
||||
+++ b/core.js
|
||||
@@ -159,7 +159,7 @@ class APIClient {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': this.getUserAgent(),
|
||||
- ...getPlatformHeaders(),
|
||||
+ // ...getPlatformHeaders(),
|
||||
...this.authHeaders(opts),
|
||||
};
|
||||
}
|
||||
diff --git a/core.mjs b/core.mjs
|
||||
index 05dbc6cfde51589a2b100d4e4b5b3c1a33b32b89..789fbb4985eb952a0349b779fa83b1a068af6e7e 100644
|
||||
--- a/core.mjs
|
||||
+++ b/core.mjs
|
||||
@@ -152,7 +152,7 @@ export class APIClient {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': this.getUserAgent(),
|
||||
- ...getPlatformHeaders(),
|
||||
+ // ...getPlatformHeaders(),
|
||||
...this.authHeaders(opts),
|
||||
};
|
||||
}
|
||||
diff --git a/error.mjs b/error.mjs
|
||||
index 7d19f5578040afa004bc887aab1725e8703d2bac..59ec725b6142299a62798ac4bdedb63ba7d9932c 100644
|
||||
--- a/error.mjs
|
||||
+++ b/error.mjs
|
||||
@@ -36,7 +36,7 @@ export class APIError extends OpenAIError {
|
||||
if (!status || !headers) {
|
||||
return new APIConnectionError({ message, cause: castToError(errorResponse) });
|
||||
}
|
||||
- const error = errorResponse?.['error'];
|
||||
+ const error = errorResponse?.['error'] || errorResponse;
|
||||
if (status === 400) {
|
||||
return new BadRequestError(status, error, message, headers);
|
||||
}
|
||||
diff --git a/resources/embeddings.js b/resources/embeddings.js
|
||||
index aae578404cb2d09a39ac33fc416f1c215c45eecd..25c54b05bdae64d5c3b36fbb30dc7c8221b14034 100644
|
||||
--- a/resources/embeddings.js
|
||||
+++ b/resources/embeddings.js
|
||||
@@ -36,6 +36,9 @@ class Embeddings extends resource_1.APIResource {
|
||||
// No encoding_format specified, defaulting to base64 for performance reasons
|
||||
// See https://github.com/openai/openai-node/pull/1312
|
||||
let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
|
||||
+ if (body.model.includes('jina')) {
|
||||
+ encoding_format = undefined;
|
||||
+ }
|
||||
if (hasUserProvidedEncodingFormat) {
|
||||
Core.debug('Request', 'User defined encoding_format:', body.encoding_format);
|
||||
}
|
||||
@@ -47,7 +50,7 @@ class Embeddings extends resource_1.APIResource {
|
||||
...options,
|
||||
});
|
||||
// if the user specified an encoding_format, return the response as-is
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
+ if (hasUserProvidedEncodingFormat || body.model.includes('jina')) {
|
||||
return response;
|
||||
}
|
||||
// in this stage, we are sure the user did not specify an encoding_format
|
||||
diff --git a/resources/embeddings.mjs b/resources/embeddings.mjs
|
||||
index 0df3c6cc79a520e54acb4c2b5f77c43b774035ff..aa488b8a11b2c413c0a663d9a6059d286d7b5faf 100644
|
||||
--- a/resources/embeddings.mjs
|
||||
+++ b/resources/embeddings.mjs
|
||||
@@ -10,6 +10,9 @@ export class Embeddings extends APIResource {
|
||||
// No encoding_format specified, defaulting to base64 for performance reasons
|
||||
// See https://github.com/openai/openai-node/pull/1312
|
||||
let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
|
||||
+ if (body.model.includes('jina')) {
|
||||
+ encoding_format = undefined;
|
||||
+ }
|
||||
if (hasUserProvidedEncodingFormat) {
|
||||
Core.debug('Request', 'User defined encoding_format:', body.encoding_format);
|
||||
}
|
||||
@@ -21,7 +24,7 @@ export class Embeddings extends APIResource {
|
||||
...options,
|
||||
});
|
||||
// if the user specified an encoding_format, return the response as-is
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
+ if (hasUserProvidedEncodingFormat || body.model.includes('jina')) {
|
||||
return response;
|
||||
}
|
||||
// in this stage, we are sure the user did not specify an encoding_format
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
diff --git a/client.js b/client.js
|
||||
index 33b4ff6309d5f29187dab4e285d07dac20340bab..8f568637ee9e4677585931fb0284c8165a933f69 100644
|
||||
--- a/client.js
|
||||
+++ b/client.js
|
||||
@@ -433,7 +433,7 @@ class OpenAI {
|
||||
'User-Agent': this.getUserAgent(),
|
||||
'X-Stainless-Retry-Count': String(retryCount),
|
||||
...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}),
|
||||
- ...(0, detect_platform_1.getPlatformHeaders)(),
|
||||
+ // ...(0, detect_platform_1.getPlatformHeaders)(),
|
||||
'OpenAI-Organization': this.organization,
|
||||
'OpenAI-Project': this.project,
|
||||
},
|
||||
diff --git a/client.mjs b/client.mjs
|
||||
index c34c18213073540ebb296ea540b1d1ad39527906..1ce1a98256d7e90e26ca963582f235b23e996e73 100644
|
||||
--- a/client.mjs
|
||||
+++ b/client.mjs
|
||||
@@ -430,7 +430,7 @@ export class OpenAI {
|
||||
'User-Agent': this.getUserAgent(),
|
||||
'X-Stainless-Retry-Count': String(retryCount),
|
||||
...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}),
|
||||
- ...getPlatformHeaders(),
|
||||
+ // ...getPlatformHeaders(),
|
||||
'OpenAI-Organization': this.organization,
|
||||
'OpenAI-Project': this.project,
|
||||
},
|
||||
diff --git a/core/error.js b/core/error.js
|
||||
index a12d9d9ccd242050161adeb0f82e1b98d9e78e20..fe3a5462480558bc426deea147f864f12b36f9bd 100644
|
||||
--- a/core/error.js
|
||||
+++ b/core/error.js
|
||||
@@ -40,7 +40,7 @@ class APIError extends OpenAIError {
|
||||
if (!status || !headers) {
|
||||
return new APIConnectionError({ message, cause: (0, errors_1.castToError)(errorResponse) });
|
||||
}
|
||||
- const error = errorResponse?.['error'];
|
||||
+ const error = errorResponse?.['error'] || errorResponse;
|
||||
if (status === 400) {
|
||||
return new BadRequestError(status, error, message, headers);
|
||||
}
|
||||
diff --git a/core/error.mjs b/core/error.mjs
|
||||
index 83cefbaffeb8c657536347322d8de9516af479a2..63334b7972ec04882aa4a0800c1ead5982345045 100644
|
||||
--- a/core/error.mjs
|
||||
+++ b/core/error.mjs
|
||||
@@ -36,7 +36,7 @@ export class APIError extends OpenAIError {
|
||||
if (!status || !headers) {
|
||||
return new APIConnectionError({ message, cause: castToError(errorResponse) });
|
||||
}
|
||||
- const error = errorResponse?.['error'];
|
||||
+ const error = errorResponse?.['error'] || errorResponse;
|
||||
if (status === 400) {
|
||||
return new BadRequestError(status, error, message, headers);
|
||||
}
|
||||
diff --git a/resources/embeddings.js b/resources/embeddings.js
|
||||
index 2404264d4ba0204322548945ebb7eab3bea82173..8f1bc45cc45e0797d50989d96b51147b90ae6790 100644
|
||||
--- a/resources/embeddings.js
|
||||
+++ b/resources/embeddings.js
|
||||
@@ -5,52 +5,64 @@ exports.Embeddings = void 0;
|
||||
const resource_1 = require("../core/resource.js");
|
||||
const utils_1 = require("../internal/utils.js");
|
||||
class Embeddings extends resource_1.APIResource {
|
||||
- /**
|
||||
- * Creates an embedding vector representing the input text.
|
||||
- *
|
||||
- * @example
|
||||
- * ```ts
|
||||
- * const createEmbeddingResponse =
|
||||
- * await client.embeddings.create({
|
||||
- * input: 'The quick brown fox jumped over the lazy dog',
|
||||
- * model: 'text-embedding-3-small',
|
||||
- * });
|
||||
- * ```
|
||||
- */
|
||||
- create(body, options) {
|
||||
- const hasUserProvidedEncodingFormat = !!body.encoding_format;
|
||||
- // No encoding_format specified, defaulting to base64 for performance reasons
|
||||
- // See https://github.com/openai/openai-node/pull/1312
|
||||
- let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
- (0, utils_1.loggerFor)(this._client).debug('embeddings/user defined encoding_format:', body.encoding_format);
|
||||
- }
|
||||
- const response = this._client.post('/embeddings', {
|
||||
- body: {
|
||||
- ...body,
|
||||
- encoding_format: encoding_format,
|
||||
- },
|
||||
- ...options,
|
||||
- });
|
||||
- // if the user specified an encoding_format, return the response as-is
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
- return response;
|
||||
- }
|
||||
- // in this stage, we are sure the user did not specify an encoding_format
|
||||
- // and we defaulted to base64 for performance reasons
|
||||
- // we are sure then that the response is base64 encoded, let's decode it
|
||||
- // the returned result will be a float32 array since this is OpenAI API's default encoding
|
||||
- (0, utils_1.loggerFor)(this._client).debug('embeddings/decoding base64 embeddings from base64');
|
||||
- return response._thenUnwrap((response) => {
|
||||
- if (response && response.data) {
|
||||
- response.data.forEach((embeddingBase64Obj) => {
|
||||
- const embeddingBase64Str = embeddingBase64Obj.embedding;
|
||||
- embeddingBase64Obj.embedding = (0, utils_1.toFloat32Array)(embeddingBase64Str);
|
||||
- });
|
||||
- }
|
||||
- return response;
|
||||
- });
|
||||
- }
|
||||
+ /**
|
||||
+ * Creates an embedding vector representing the input text.
|
||||
+ *
|
||||
+ * @example
|
||||
+ * ```ts
|
||||
+ * const createEmbeddingResponse =
|
||||
+ * await client.embeddings.create({
|
||||
+ * input: 'The quick brown fox jumped over the lazy dog',
|
||||
+ * model: 'text-embedding-3-small',
|
||||
+ * });
|
||||
+ * ```
|
||||
+ */
|
||||
+ create(body, options) {
|
||||
+ const hasUserProvidedEncodingFormat = !!body.encoding_format;
|
||||
+ // No encoding_format specified, defaulting to base64 for performance reasons
|
||||
+ // See https://github.com/openai/openai-node/pull/1312
|
||||
+ let encoding_format = hasUserProvidedEncodingFormat
|
||||
+ ? body.encoding_format
|
||||
+ : "base64";
|
||||
+ if (body.model.includes("jina")) {
|
||||
+ encoding_format = undefined;
|
||||
+ }
|
||||
+ if (hasUserProvidedEncodingFormat) {
|
||||
+ (0, utils_1.loggerFor)(this._client).debug(
|
||||
+ "embeddings/user defined encoding_format:",
|
||||
+ body.encoding_format
|
||||
+ );
|
||||
+ }
|
||||
+ const response = this._client.post("/embeddings", {
|
||||
+ body: {
|
||||
+ ...body,
|
||||
+ encoding_format: encoding_format,
|
||||
+ },
|
||||
+ ...options,
|
||||
+ });
|
||||
+ // if the user specified an encoding_format, return the response as-is
|
||||
+ if (hasUserProvidedEncodingFormat || body.model.includes("jina")) {
|
||||
+ return response;
|
||||
+ }
|
||||
+ // in this stage, we are sure the user did not specify an encoding_format
|
||||
+ // and we defaulted to base64 for performance reasons
|
||||
+ // we are sure then that the response is base64 encoded, let's decode it
|
||||
+ // the returned result will be a float32 array since this is OpenAI API's default encoding
|
||||
+ (0, utils_1.loggerFor)(this._client).debug(
|
||||
+ "embeddings/decoding base64 embeddings from base64"
|
||||
+ );
|
||||
+ return response._thenUnwrap((response) => {
|
||||
+ if (response && response.data) {
|
||||
+ response.data.forEach((embeddingBase64Obj) => {
|
||||
+ const embeddingBase64Str = embeddingBase64Obj.embedding;
|
||||
+ embeddingBase64Obj.embedding = (0, utils_1.toFloat32Array)(
|
||||
+ embeddingBase64Str
|
||||
+ );
|
||||
+ });
|
||||
+ }
|
||||
+ return response;
|
||||
+ });
|
||||
+ }
|
||||
}
|
||||
exports.Embeddings = Embeddings;
|
||||
//# sourceMappingURL=embeddings.js.map
|
||||
diff --git a/resources/embeddings.mjs b/resources/embeddings.mjs
|
||||
index 19dcaef578c194a89759c4360073cfd4f7dd2cbf..0284e9cc615c900eff508eb595f7360a74bd9200 100644
|
||||
--- a/resources/embeddings.mjs
|
||||
+++ b/resources/embeddings.mjs
|
||||
@@ -2,51 +2,61 @@
|
||||
import { APIResource } from "../core/resource.mjs";
|
||||
import { loggerFor, toFloat32Array } from "../internal/utils.mjs";
|
||||
export class Embeddings extends APIResource {
|
||||
- /**
|
||||
- * Creates an embedding vector representing the input text.
|
||||
- *
|
||||
- * @example
|
||||
- * ```ts
|
||||
- * const createEmbeddingResponse =
|
||||
- * await client.embeddings.create({
|
||||
- * input: 'The quick brown fox jumped over the lazy dog',
|
||||
- * model: 'text-embedding-3-small',
|
||||
- * });
|
||||
- * ```
|
||||
- */
|
||||
- create(body, options) {
|
||||
- const hasUserProvidedEncodingFormat = !!body.encoding_format;
|
||||
- // No encoding_format specified, defaulting to base64 for performance reasons
|
||||
- // See https://github.com/openai/openai-node/pull/1312
|
||||
- let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
- loggerFor(this._client).debug('embeddings/user defined encoding_format:', body.encoding_format);
|
||||
- }
|
||||
- const response = this._client.post('/embeddings', {
|
||||
- body: {
|
||||
- ...body,
|
||||
- encoding_format: encoding_format,
|
||||
- },
|
||||
- ...options,
|
||||
- });
|
||||
- // if the user specified an encoding_format, return the response as-is
|
||||
- if (hasUserProvidedEncodingFormat) {
|
||||
- return response;
|
||||
- }
|
||||
- // in this stage, we are sure the user did not specify an encoding_format
|
||||
- // and we defaulted to base64 for performance reasons
|
||||
- // we are sure then that the response is base64 encoded, let's decode it
|
||||
- // the returned result will be a float32 array since this is OpenAI API's default encoding
|
||||
- loggerFor(this._client).debug('embeddings/decoding base64 embeddings from base64');
|
||||
- return response._thenUnwrap((response) => {
|
||||
- if (response && response.data) {
|
||||
- response.data.forEach((embeddingBase64Obj) => {
|
||||
- const embeddingBase64Str = embeddingBase64Obj.embedding;
|
||||
- embeddingBase64Obj.embedding = toFloat32Array(embeddingBase64Str);
|
||||
- });
|
||||
- }
|
||||
- return response;
|
||||
- });
|
||||
- }
|
||||
+ /**
|
||||
+ * Creates an embedding vector representing the input text.
|
||||
+ *
|
||||
+ * @example
|
||||
+ * ```ts
|
||||
+ * const createEmbeddingResponse =
|
||||
+ * await client.embeddings.create({
|
||||
+ * input: 'The quick brown fox jumped over the lazy dog',
|
||||
+ * model: 'text-embedding-3-small',
|
||||
+ * });
|
||||
+ * ```
|
||||
+ */
|
||||
+ create(body, options) {
|
||||
+ const hasUserProvidedEncodingFormat = !!body.encoding_format;
|
||||
+ // No encoding_format specified, defaulting to base64 for performance reasons
|
||||
+ // See https://github.com/openai/openai-node/pull/1312
|
||||
+ let encoding_format = hasUserProvidedEncodingFormat
|
||||
+ ? body.encoding_format
|
||||
+ : "base64";
|
||||
+ if (body.model.includes("jina")) {
|
||||
+ encoding_format = undefined;
|
||||
+ }
|
||||
+ if (hasUserProvidedEncodingFormat) {
|
||||
+ loggerFor(this._client).debug(
|
||||
+ "embeddings/user defined encoding_format:",
|
||||
+ body.encoding_format
|
||||
+ );
|
||||
+ }
|
||||
+ const response = this._client.post("/embeddings", {
|
||||
+ body: {
|
||||
+ ...body,
|
||||
+ encoding_format: encoding_format,
|
||||
+ },
|
||||
+ ...options,
|
||||
+ });
|
||||
+ // if the user specified an encoding_format, return the response as-is
|
||||
+ if (hasUserProvidedEncodingFormat || body.model.includes("jina")) {
|
||||
+ return response;
|
||||
+ }
|
||||
+ // in this stage, we are sure the user did not specify an encoding_format
|
||||
+ // and we defaulted to base64 for performance reasons
|
||||
+ // we are sure then that the response is base64 encoded, let's decode it
|
||||
+ // the returned result will be a float32 array since this is OpenAI API's default encoding
|
||||
+ loggerFor(this._client).debug(
|
||||
+ "embeddings/decoding base64 embeddings from base64"
|
||||
+ );
|
||||
+ return response._thenUnwrap((response) => {
|
||||
+ if (response && response.data) {
|
||||
+ response.data.forEach((embeddingBase64Obj) => {
|
||||
+ const embeddingBase64Str = embeddingBase64Obj.embedding;
|
||||
+ embeddingBase64Obj.embedding = toFloat32Array(embeddingBase64Str);
|
||||
+ });
|
||||
+ }
|
||||
+ return response;
|
||||
+ });
|
||||
+ }
|
||||
}
|
||||
//# sourceMappingURL=embeddings.mjs.map
|
||||
+4
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "1.5.0-rc.1",
|
||||
"version": "1.4.1",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -185,7 +185,7 @@
|
||||
"mime": "^4.0.4",
|
||||
"motion": "^12.10.5",
|
||||
"npx-scope-finder": "^1.2.0",
|
||||
"openai": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",
|
||||
"openai": "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch",
|
||||
"p-queue": "^8.1.0",
|
||||
"playwright": "^1.52.0",
|
||||
"prettier": "^3.5.3",
|
||||
@@ -226,11 +226,10 @@
|
||||
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
|
||||
"@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
|
||||
"libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch",
|
||||
"openai@npm:^4.77.0": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",
|
||||
"canvas": "3.1.0",
|
||||
"openai@npm:^4.77.0": "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch",
|
||||
"pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch",
|
||||
"app-builder-lib@npm:26.0.13": "patch:app-builder-lib@npm%3A26.0.13#~/.yarn/patches/app-builder-lib-npm-26.0.13-a064c9e1d0.patch",
|
||||
"openai@npm:^4.87.3": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",
|
||||
"openai@npm:^4.87.3": "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch",
|
||||
"app-builder-lib@npm:26.0.15": "patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch",
|
||||
"@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A0.3.44#~/.yarn/patches/@langchain-core-npm-0.3.44-41d5c3cb0a.patch"
|
||||
},
|
||||
|
||||
@@ -841,8 +841,9 @@ export class SelectionService {
|
||||
//ctrlkey pressed
|
||||
if (this.lastCtrlkeyDownTime === 0) {
|
||||
this.lastCtrlkeyDownTime = Date.now()
|
||||
//add the mouse-wheel listener, detect if user is zooming in/out
|
||||
//add the mouse-wheel&mouse-down listener, detect if user is zooming in/out or multi-selecting
|
||||
this.selectionHook!.on('mouse-wheel', this.handleMouseWheelCtrlkeyMode)
|
||||
this.selectionHook!.on('mouse-down', this.handleMouseDownCtrlkeyMode)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -866,8 +867,9 @@ export class SelectionService {
|
||||
*/
|
||||
private handleKeyUpCtrlkeyMode = (data: KeyboardEventData) => {
|
||||
if (!this.isCtrlkey(data.vkCode)) return
|
||||
//remove the mouse-wheel listener
|
||||
//remove the mouse-wheel&mouse-down listener
|
||||
this.selectionHook!.off('mouse-wheel', this.handleMouseWheelCtrlkeyMode)
|
||||
this.selectionHook!.off('mouse-down', this.handleMouseDownCtrlkeyMode)
|
||||
this.lastCtrlkeyDownTime = 0
|
||||
}
|
||||
|
||||
@@ -880,6 +882,15 @@ export class SelectionService {
|
||||
this.lastCtrlkeyDownTime = -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse down events in ctrlkey trigger mode
|
||||
* ignore CtrlKey pressing when mouse down is used
|
||||
* because user is multi-selecting
|
||||
*/
|
||||
private handleMouseDownCtrlkeyMode = () => {
|
||||
this.lastCtrlkeyDownTime = -1
|
||||
}
|
||||
|
||||
//check if the key is ctrl key
|
||||
private isCtrlkey(vkCode: number) {
|
||||
return vkCode === 162 || vkCode === 163
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
--color-reference-text: #ffffff;
|
||||
--color-reference-background: #0b0e12;
|
||||
|
||||
--color-list-item: #222;
|
||||
--color-list-item-hover: #1e1e1e;
|
||||
|
||||
--modal-background: #1f1f1f;
|
||||
|
||||
--color-highlight: rgba(0, 0, 0, 1);
|
||||
@@ -68,7 +71,7 @@
|
||||
--chat-background-assistant: #2c2c2c;
|
||||
--chat-text-user: var(--color-black);
|
||||
|
||||
--list-item-border-radius: 16px;
|
||||
--list-item-border-radius: 20px;
|
||||
}
|
||||
|
||||
[theme-mode='light'] {
|
||||
@@ -117,6 +120,9 @@
|
||||
--color-reference-text: #000000;
|
||||
--color-reference-background: #f1f7ff;
|
||||
|
||||
--color-list-item: #eee;
|
||||
--color-list-item-hover: #f5f5f5;
|
||||
|
||||
--modal-background: var(--color-white);
|
||||
|
||||
--color-highlight: initial;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||
import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
|
||||
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { ThemeMode } from '@renderer/types'
|
||||
import { isEmoji } from '@renderer/utils'
|
||||
import type { MenuProps } from 'antd'
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
Folder,
|
||||
Languages,
|
||||
LayoutGrid,
|
||||
MessageSquareQuote,
|
||||
MessageSquare,
|
||||
Moon,
|
||||
Palette,
|
||||
Settings,
|
||||
@@ -35,7 +36,6 @@ import styled from 'styled-components'
|
||||
import DragableList from '../DragableList'
|
||||
import MinAppIcon from '../Icons/MinAppIcon'
|
||||
import UserPopup from '../Popups/UserPopup'
|
||||
import i18n from '@renderer/i18n'
|
||||
|
||||
const Sidebar: FC = () => {
|
||||
const { hideMinappPopup, openMinapp } = useMinappPopup()
|
||||
@@ -67,9 +67,7 @@ const Sidebar: FC = () => {
|
||||
openMinapp({
|
||||
id: docsId,
|
||||
name: t('docs.title'),
|
||||
url: isChinese
|
||||
? 'https://docs.cherry-ai.com/'
|
||||
: 'https://docs.cherry-ai.com/cherry-studio-wen-dang/en-us',
|
||||
url: isChinese ? 'https://docs.cherry-ai.com/' : 'https://docs.cherry-ai.com/cherry-studio-wen-dang/en-us',
|
||||
logo: AppLogo
|
||||
})
|
||||
}
|
||||
@@ -151,7 +149,7 @@ const MainMenus: FC = () => {
|
||||
const isRoutes = (path: string): string => (pathname.startsWith(path) && !minappShow ? 'active' : '')
|
||||
|
||||
const iconMap = {
|
||||
assistants: <MessageSquareQuote size={18} className="icon" />,
|
||||
assistants: <MessageSquare size={18} className="icon" />,
|
||||
agents: <Sparkle size={18} className="icon" />,
|
||||
paintings: <Palette size={18} className="icon" />,
|
||||
translate: <Languages size={18} className="icon" />,
|
||||
|
||||
@@ -48,6 +48,10 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
},
|
||||
ColorPicker: {
|
||||
fontFamily: 'var(--code-font-family)'
|
||||
},
|
||||
Segmented: {
|
||||
itemActiveBg: 'var(--color-background-mute)',
|
||||
itemHoverBg: 'var(--color-background-mute)'
|
||||
}
|
||||
},
|
||||
token: {
|
||||
|
||||
@@ -10,16 +10,19 @@ export function usePaintings() {
|
||||
const edit = useAppSelector((state) => state.paintings.edit)
|
||||
const upscale = useAppSelector((state) => state.paintings.upscale)
|
||||
const DMXAPIPaintings = useAppSelector((state) => state.paintings.DMXAPIPaintings)
|
||||
const tokenFluxPaintings = useAppSelector((state) => state.paintings.tokenFluxPaintings)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return {
|
||||
paintings,
|
||||
DMXAPIPaintings,
|
||||
tokenFluxPaintings,
|
||||
persistentData: {
|
||||
generate,
|
||||
remix,
|
||||
edit,
|
||||
upscale
|
||||
upscale,
|
||||
tokenFluxPaintings
|
||||
},
|
||||
addPainting: (namespace: keyof PaintingsState, painting: PaintingAction) => {
|
||||
dispatch(addPainting({ namespace, painting }))
|
||||
|
||||
@@ -960,7 +960,17 @@
|
||||
"text_desc_required": "Please enter image description first",
|
||||
"req_error_text": "Operation failed. Please try again. Avoid using 'copyrighted' or 'sensitive' words in your prompt.",
|
||||
"auto_create_paint": "Auto-create image",
|
||||
"auto_create_paint_tip": "After the image is generated, a new image will be created automatically."
|
||||
"auto_create_paint_tip": "After the image is generated, a new image will be created automatically.",
|
||||
"select_model": "Select Model",
|
||||
"input_parameters": "Input Parameters",
|
||||
"input_image": "Input Image",
|
||||
"generated_image": "Generated Image",
|
||||
"pricing": "Pricing",
|
||||
"model_and_pricing": "Model & Pricing",
|
||||
"per_image": "per image",
|
||||
"per_images": "per images",
|
||||
"required_field": "Required field",
|
||||
"uploaded_input": "Uploaded input"
|
||||
},
|
||||
"prompts": {
|
||||
"explanation": "Explain this concept to me",
|
||||
|
||||
@@ -960,7 +960,17 @@
|
||||
"text_desc_required": "画像の説明を先に入力してください",
|
||||
"req_error_text": "実行に失敗しました。もう一度お試しください。プロンプトに「著作権用語」や「センシティブな用語」を含めないでください。",
|
||||
"auto_create_paint": "画像を自動作成",
|
||||
"auto_create_paint_tip": "画像が生成された後、自動的に新しい画像が作成されます。"
|
||||
"auto_create_paint_tip": "画像が生成された後、自動的に新しい画像が作成されます。",
|
||||
"select_model": "モデルを選択",
|
||||
"input_parameters": "パラメータ入力",
|
||||
"input_image": "入力画像",
|
||||
"generated_image": "生成画像",
|
||||
"pricing": "料金",
|
||||
"model_and_pricing": "モデルと料金",
|
||||
"per_image": "1枚あたり",
|
||||
"per_images": "複数枚あたり",
|
||||
"required_field": "必須項目",
|
||||
"uploaded_input": "アップロード済みの入力"
|
||||
},
|
||||
"prompts": {
|
||||
"explanation": "この概念を説明してください",
|
||||
@@ -1128,26 +1138,6 @@
|
||||
"markdown_export.show_model_provider.help": "Markdownエクスポート時にモデルプロバイダー(例:OpenAI、Geminiなど)を表示します。",
|
||||
"minute_interval_one": "{{count}} 分",
|
||||
"minute_interval_other": "{{count}} 分",
|
||||
"notion": {
|
||||
"api_key": "Notion APIキー",
|
||||
"api_key_placeholder": "Notion APIキーを入力してください",
|
||||
"check": {
|
||||
"button": "確認",
|
||||
"empty_api_key": "Api_keyが設定されていません",
|
||||
"empty_database_id": "Database_idが設定されていません",
|
||||
"error": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
|
||||
"fail": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
|
||||
"success": "接続に成功しました。"
|
||||
},
|
||||
"database_id": "Notion データベースID",
|
||||
"database_id_placeholder": "Notion データベースIDを入力してください",
|
||||
"help": "Notion 設定ドキュメント",
|
||||
"page_name_key": "ページタイトルフィールド名",
|
||||
"page_name_key_placeholder": "ページタイトルフィールド名を入力してください。デフォルトは Name です",
|
||||
"title": "Notion 設定",
|
||||
"export_reasoning.title": "エクスポート時に思考チェーンを含める",
|
||||
"export_reasoning.help": "有効にすると、Notionにエクスポートする際に思考チェーンの内容が含まれます。"
|
||||
},
|
||||
"title": "データ設定",
|
||||
"webdav": {
|
||||
"autoSync": "自動バックアップ",
|
||||
@@ -1267,7 +1257,25 @@
|
||||
"new_folder.button": "新しいフォルダー"
|
||||
},
|
||||
"message_title.use_topic_naming.title": "トピック命名モデルを使用してメッセージのタイトルを作成",
|
||||
"message_title.use_topic_naming.help": "この設定は、すべてのMarkdownエクスポート方法に影響します。"
|
||||
"message_title.use_topic_naming.help": "この設定は、すべてのMarkdownエクスポート方法に影響します。",
|
||||
"notion.api_key": "Notion APIキー",
|
||||
"notion.api_key_placeholder": "Notion APIキーを入力してください",
|
||||
"notion.check": {
|
||||
"button": "確認",
|
||||
"empty_api_key": "Api_keyが設定されていません",
|
||||
"empty_database_id": "Database_idが設定されていません",
|
||||
"error": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
|
||||
"fail": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください",
|
||||
"success": "接続に成功しました。"
|
||||
},
|
||||
"notion.database_id": "Notion データベースID",
|
||||
"notion.database_id_placeholder": "Notion データベースIDを入力してください",
|
||||
"notion.help": "Notion 設定ドキュメント",
|
||||
"notion.page_name_key": "ページタイトルフィールド名",
|
||||
"notion.page_name_key_placeholder": "ページタイトルフィールド名を入力してください。デフォルトは Name です",
|
||||
"notion.title": "Notion 設定",
|
||||
"notion.export_reasoning.title": "エクスポート時に思考チェーンを含める",
|
||||
"notion.export_reasoning.help": "有効にすると、Notionにエクスポートする際に思考チェーンの内容が含まれます。"
|
||||
},
|
||||
"display.assistant.title": "アシスタント設定",
|
||||
"display.custom.css": "カスタムCSS",
|
||||
|
||||
@@ -960,7 +960,17 @@
|
||||
"text_desc_required": "Пожалуйста, сначала введите описание изображения",
|
||||
"req_error_text": "Операция не удалась, повторите попытку. Пожалуйста, избегайте защищенных авторским правом терминов и конфиденциальных слов в запросах.",
|
||||
"auto_create_paint": "Автоматическое создание изображения",
|
||||
"auto_create_paint_tip": "После генерации изображения будет автоматически создано новое."
|
||||
"auto_create_paint_tip": "После генерации изображения будет автоматически создано новое.",
|
||||
"select_model": "Выбрать модель",
|
||||
"input_parameters": "Ввести параметры",
|
||||
"input_image": "Входное изображение",
|
||||
"generated_image": "Сгенерированное изображение",
|
||||
"pricing": "Цены",
|
||||
"model_and_pricing": "Модель и цены",
|
||||
"per_image": "за изображение",
|
||||
"per_images": "за изображения",
|
||||
"required_field": "Обязательное поле",
|
||||
"uploaded_input": "Загруженный ввод"
|
||||
},
|
||||
"prompts": {
|
||||
"explanation": "Объясните мне этот концепт",
|
||||
|
||||
@@ -960,7 +960,17 @@
|
||||
"text_desc_required": "请先输入图片描述",
|
||||
"req_error_text": "运行失败,请重试。提示词避免“版权词”和”敏感词”哦。",
|
||||
"auto_create_paint": "自动新建图片",
|
||||
"auto_create_paint_tip": "在图片生成后,会自动新建图片"
|
||||
"auto_create_paint_tip": "在图片生成后,会自动新建图片",
|
||||
"select_model": "选择模型",
|
||||
"input_parameters": "输入参数",
|
||||
"input_image": "输入图片",
|
||||
"generated_image": "生成图片",
|
||||
"pricing": "定价",
|
||||
"model_and_pricing": "模型与定价",
|
||||
"per_image": "每张图片",
|
||||
"per_images": "每张图片",
|
||||
"required_field": "必填项",
|
||||
"uploaded_input": "已上传输入"
|
||||
},
|
||||
"prompts": {
|
||||
"explanation": "帮我解释一下这个概念",
|
||||
|
||||
@@ -960,7 +960,17 @@
|
||||
"text_desc_required": "請先輸入圖片描述",
|
||||
"req_error_text": "运行失败,请重试。提示词避免“版权词”和”敏感词”哦。",
|
||||
"auto_create_paint": "自動新增圖片",
|
||||
"auto_create_paint_tip": "圖片生成後,會自動新增圖片"
|
||||
"auto_create_paint_tip": "圖片生成後,會自動新增圖片",
|
||||
"select_model": "選擇模型",
|
||||
"input_parameters": "輸入參數",
|
||||
"input_image": "輸入圖片",
|
||||
"generated_image": "生成圖片",
|
||||
"pricing": "定價",
|
||||
"model_and_pricing": "模型與定價",
|
||||
"per_image": "每張圖片",
|
||||
"per_images": "每張圖片",
|
||||
"required_field": "必填欄位",
|
||||
"uploaded_input": "已上傳輸入"
|
||||
},
|
||||
"prompts": {
|
||||
"explanation": "幫我解釋一下這個概念",
|
||||
|
||||
@@ -34,7 +34,7 @@ const Container = styled.div<{ $isDark: boolean }>`
|
||||
margin: 5px 20px 0 20px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--color-border);
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
const Text = styled.div`
|
||||
|
||||
@@ -58,12 +58,14 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
{getGroupedAssistants.map((group) => (
|
||||
<TagsContainer key={group.tag}>
|
||||
<GroupTitle>
|
||||
<Tooltip title={group.tag}>
|
||||
<GroupTitleName>{group.tag}</GroupTitleName>
|
||||
</Tooltip>
|
||||
<Divider style={{ margin: '12px 0' }}></Divider>
|
||||
</GroupTitle>
|
||||
{group.tag !== t('assistants.tags.untagged') && (
|
||||
<GroupTitle>
|
||||
<Tooltip title={group.tag}>
|
||||
<GroupTitleName>{group.tag}</GroupTitleName>
|
||||
</Tooltip>
|
||||
<Divider style={{ margin: '12px 0' }}></Divider>
|
||||
</GroupTitle>
|
||||
)}
|
||||
{group.assistants.map((assistant) => (
|
||||
<AssistantItem
|
||||
key={assistant.id}
|
||||
|
||||
@@ -60,7 +60,7 @@ import {
|
||||
} from '@renderer/types'
|
||||
import { modalConfirm } from '@renderer/utils'
|
||||
import { Button, Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { CircleHelp, RotateCcw, Settings2 } from 'lucide-react'
|
||||
import { CircleHelp, Settings2 } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@@ -72,7 +72,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const SettingsTab: FC<Props> = (props) => {
|
||||
const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
|
||||
const { assistant, updateAssistantSettings } = useAssistant(props.assistant.id)
|
||||
const { provider } = useProvider(assistant.model.provider)
|
||||
|
||||
const { messageStyle, fontSize, language } = useSettings()
|
||||
@@ -140,24 +140,6 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setTemperature(DEFAULT_TEMPERATURE)
|
||||
setContextCount(DEFAULT_CONTEXTCOUNT)
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
settings: {
|
||||
...assistant.settings,
|
||||
temperature: DEFAULT_TEMPERATURE,
|
||||
contextCount: DEFAULT_CONTEXTCOUNT,
|
||||
enableMaxTokens: false,
|
||||
maxTokens: DEFAULT_MAX_TOKENS,
|
||||
streamOutput: true,
|
||||
hideMessages: false,
|
||||
customParameters: []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const codeStyle = useMemo(() => {
|
||||
return codeEditor.enabled
|
||||
? theme === ThemeMode.light
|
||||
@@ -211,14 +193,6 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
defaultExpanded={true}
|
||||
extra={
|
||||
<HStack alignItems="center" gap={2}>
|
||||
<Tooltip title={t('chat.settings.reset')}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={onReset}
|
||||
icon={<RotateCcw size={20} style={{ cursor: 'pointer', padding: '0 3px', opacity: 0.8 }} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
@@ -714,6 +688,7 @@ const Container = styled(Scrollbar)`
|
||||
padding-right: 0;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 3px;
|
||||
`
|
||||
|
||||
const SettingRowTitleSmall = styled(SettingRowTitle)`
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
FolderOutlined,
|
||||
MenuOutlined,
|
||||
PushpinOutlined,
|
||||
QuestionCircleOutlined,
|
||||
UploadOutlined
|
||||
@@ -54,7 +55,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
const { assistants } = useAssistants()
|
||||
const { assistant, removeTopic, moveTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
|
||||
const { t } = useTranslation()
|
||||
const { showTopicTime, pinTopicsToTop } = useSettings()
|
||||
const { showTopicTime, pinTopicsToTop, setTopicPosition } = useSettings()
|
||||
|
||||
const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)'
|
||||
|
||||
@@ -248,6 +249,23 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('settings.topic.position'),
|
||||
key: 'topic-position',
|
||||
icon: <MenuOutlined />,
|
||||
children: [
|
||||
{
|
||||
label: t('settings.topic.position.left'),
|
||||
key: 'left',
|
||||
onClick: () => setTopicPosition('left')
|
||||
},
|
||||
{
|
||||
label: t('settings.topic.position.right'),
|
||||
key: 'right',
|
||||
onClick: () => setTopicPosition('right')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: t('chat.topics.copy.title'),
|
||||
key: 'copy',
|
||||
@@ -363,26 +381,27 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
||||
|
||||
return menus
|
||||
}, [
|
||||
activeTopic.id,
|
||||
assistant,
|
||||
assistants,
|
||||
exportMenuOptions.docx,
|
||||
targetTopic,
|
||||
t,
|
||||
exportMenuOptions.image,
|
||||
exportMenuOptions.joplin,
|
||||
exportMenuOptions.markdown,
|
||||
exportMenuOptions.markdown_reason,
|
||||
exportMenuOptions.docx,
|
||||
exportMenuOptions.notion,
|
||||
exportMenuOptions.obsidian,
|
||||
exportMenuOptions.siyuan,
|
||||
exportMenuOptions.yuque,
|
||||
onClearMessages,
|
||||
onDeleteTopic,
|
||||
onMoveTopic,
|
||||
onPinTopic,
|
||||
setActiveTopic,
|
||||
t,
|
||||
exportMenuOptions.obsidian,
|
||||
exportMenuOptions.joplin,
|
||||
exportMenuOptions.siyuan,
|
||||
assistants,
|
||||
assistant,
|
||||
updateTopic,
|
||||
targetTopic
|
||||
activeTopic.id,
|
||||
setActiveTopic,
|
||||
onPinTopic,
|
||||
onClearMessages,
|
||||
setTopicPosition,
|
||||
onMoveTopic,
|
||||
onDeleteTopic
|
||||
])
|
||||
|
||||
// Sort topics based on pinned status if pinTopicsToTop is enabled
|
||||
@@ -486,7 +505,6 @@ const TopicListItem = styled.div`
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 0.5px solid transparent;
|
||||
position: relative;
|
||||
width: calc(var(--assistants-width) - 20px);
|
||||
.menu {
|
||||
@@ -494,15 +512,10 @@ const TopicListItem = styled.div`
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
.name {
|
||||
}
|
||||
background-color: var(--color-list-item-hover);
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-background-soft);
|
||||
border: 0.5px solid var(--color-border);
|
||||
.name {
|
||||
}
|
||||
background-color: var(--color-list-item);
|
||||
.menu {
|
||||
opacity: 1;
|
||||
&:hover {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CheckOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
MinusCircleOutlined,
|
||||
@@ -185,10 +186,9 @@ const handleTagOperation = (
|
||||
updateAssistants: (assistants: Assistant[]) => void
|
||||
) => {
|
||||
if (assistant.tags?.includes(tag)) {
|
||||
updateAssistants(assistants.map((a) => (a.id === assistant.id ? { ...a, tags: [] } : a)))
|
||||
} else {
|
||||
updateAssistants(assistants.map((a) => (a.id === assistant.id ? { ...a, tags: [tag] } : a)))
|
||||
return
|
||||
}
|
||||
updateAssistants(assistants.map((a) => (a.id === assistant.id ? { ...a, tags: [tag] } : a)))
|
||||
}
|
||||
|
||||
// 提取创建菜单项的函数
|
||||
@@ -202,8 +202,7 @@ const createTagMenuItems = (
|
||||
const items: MenuProps['items'] = [
|
||||
...allTags.map((tag) => ({
|
||||
label: tag,
|
||||
icon: assistant.tags?.includes(tag) ? <DeleteOutlined size={14} /> : <Tag size={12} />,
|
||||
danger: assistant.tags?.includes(tag),
|
||||
icon: assistant.tags?.includes(tag) ? <CheckOutlined size={14} /> : <Tag size={12} />,
|
||||
key: `all-tag-${tag}`,
|
||||
onClick: () => handleTagOperation(tag, assistant, assistants, updateAssistants)
|
||||
}))
|
||||
@@ -383,23 +382,18 @@ const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
padding: 0 8px;
|
||||
height: 37px;
|
||||
position: relative;
|
||||
border-radius: var(--list-item-border-radius);
|
||||
border: 0.5px solid transparent;
|
||||
width: calc(var(--assistants-width) - 20px);
|
||||
cursor: pointer;
|
||||
.iconfont {
|
||||
opacity: 0;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
background-color: var(--color-list-item-hover);
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-background-soft);
|
||||
border: 0.5px solid var(--color-border);
|
||||
background-color: var(--color-list-item);
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ const HomeTabs: FC<Props> = ({
|
||||
const assistantTab = {
|
||||
label: t('assistants.abbr'),
|
||||
value: 'assistants'
|
||||
// icon: <BotIcon size={16} />
|
||||
}
|
||||
|
||||
const onCreateAssistant = async () => {
|
||||
@@ -104,28 +105,35 @@ const HomeTabs: FC<Props> = ({
|
||||
return (
|
||||
<Container style={{ ...border, ...style }} className="home-tabs">
|
||||
{(showTab || (forceToSeeAllTab == true && !showTopics)) && (
|
||||
<Segmented
|
||||
value={tab}
|
||||
style={{ borderRadius: 16, paddingTop: 10, margin: '0 10px', gap: 2 }}
|
||||
options={
|
||||
[
|
||||
(position === 'left' && topicPosition === 'left') || (forceToSeeAllTab == true && position === 'left')
|
||||
? assistantTab
|
||||
: undefined,
|
||||
{
|
||||
label: t('common.topics'),
|
||||
value: 'topic'
|
||||
},
|
||||
{
|
||||
label: t('settings.title'),
|
||||
value: 'settings'
|
||||
}
|
||||
].filter(Boolean) as SegmentedProps['options']
|
||||
}
|
||||
onChange={(value) => setTab(value as 'topic' | 'settings')}
|
||||
block
|
||||
/>
|
||||
<>
|
||||
<Segmented
|
||||
value={tab}
|
||||
style={{ borderRadius: 50 }}
|
||||
shape="round"
|
||||
options={
|
||||
[
|
||||
(position === 'left' && topicPosition === 'left') || (forceToSeeAllTab == true && position === 'left')
|
||||
? assistantTab
|
||||
: undefined,
|
||||
{
|
||||
label: t('common.topics'),
|
||||
value: 'topic'
|
||||
// icon: <MessageSquareQuote size={16} />
|
||||
},
|
||||
{
|
||||
label: t('settings.title'),
|
||||
value: 'settings'
|
||||
// icon: <SettingsIcon size={16} />
|
||||
}
|
||||
].filter(Boolean) as SegmentedProps['options']
|
||||
}
|
||||
onChange={(value) => setTab(value as 'topic' | 'settings')}
|
||||
block
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<TabContent className="home-tabs-content">
|
||||
{tab === 'assistants' && (
|
||||
<Assistants
|
||||
@@ -149,7 +157,7 @@ const Container = styled.div`
|
||||
flex-direction: column;
|
||||
max-width: var(--assistants-width);
|
||||
min-width: var(--assistants-width);
|
||||
background-color: var(--color-background);
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
.collapsed {
|
||||
width: 0;
|
||||
@@ -165,14 +173,21 @@ const TabContent = styled.div`
|
||||
overflow-x: hidden;
|
||||
`
|
||||
|
||||
const Divider = styled.div`
|
||||
border-top: 0.5px solid var(--color-border);
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const Segmented = styled(AntSegmented)`
|
||||
font-family: var(--font-family);
|
||||
|
||||
&.ant-segmented {
|
||||
background-color: transparent;
|
||||
border-radius: 0 !important;
|
||||
border-bottom: 0.5px solid var(--color-border);
|
||||
padding-bottom: 10px;
|
||||
margin: 0 10px;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
.ant-segmented-item {
|
||||
overflow: hidden;
|
||||
@@ -184,10 +199,10 @@ const Segmented = styled(AntSegmented)`
|
||||
border-radius: var(--list-item-border-radius);
|
||||
box-shadow: none;
|
||||
}
|
||||
.ant-segmented-item-selected {
|
||||
background-color: var(--color-background-soft);
|
||||
border: 0.5px solid var(--color-border);
|
||||
.ant-segmented-item-selected,
|
||||
.ant-segmented-item-selected:active {
|
||||
transition: none !important;
|
||||
background-color: var(--color-list-item);
|
||||
}
|
||||
.ant-segmented-item-label {
|
||||
align-items: center;
|
||||
@@ -200,25 +215,17 @@ const Segmented = styled(AntSegmented)`
|
||||
.ant-segmented-item-label[aria-selected='true'] {
|
||||
color: var(--color-text);
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 13px;
|
||||
margin-left: -2px;
|
||||
}
|
||||
.anticon-setting {
|
||||
font-size: 12px;
|
||||
}
|
||||
.icon-business-smart-assistant {
|
||||
margin-right: -2px;
|
||||
}
|
||||
.ant-segmented-item-icon + * {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.ant-segmented-thumb {
|
||||
transition: none !important;
|
||||
background-color: var(--color-background-soft);
|
||||
border: 0.5px solid var(--color-border);
|
||||
background-color: var(--color-list-item);
|
||||
border-radius: var(--list-item-border-radius);
|
||||
box-shadow: none;
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
.ant-segmented-item-label,
|
||||
.ant-segmented-item-icon {
|
||||
|
||||
@@ -7,8 +7,9 @@ import { Route, Routes, useParams } from 'react-router-dom'
|
||||
import AihubmixPage from './AihubmixPage'
|
||||
import DmxapiPage from './DmxapiPage'
|
||||
import SiliconPage from './SiliconPage'
|
||||
import TokenFluxPage from './TokenFluxPage'
|
||||
|
||||
const Options = ['aihubmix', 'silicon', 'dmxapi']
|
||||
const Options = ['aihubmix', 'silicon', 'dmxapi', 'tokenflux']
|
||||
|
||||
const PaintingsRoutePage: FC = () => {
|
||||
const params = useParams()
|
||||
@@ -28,6 +29,7 @@ const PaintingsRoutePage: FC = () => {
|
||||
<Route path="/aihubmix" element={<AihubmixPage Options={Options} />} />
|
||||
<Route path="/silicon" element={<SiliconPage Options={Options} />} />
|
||||
<Route path="/dmxapi" element={<DmxapiPage Options={Options} />} />
|
||||
<Route path="/tokenflux" element={<TokenFluxPage Options={Options} />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,786 @@
|
||||
import { PlusOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { getProviderLogo } from '@renderer/config/providers'
|
||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import FileManager from '@renderer/services/FileManager'
|
||||
import { translateText } from '@renderer/services/TranslateService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import type { TokenFluxPainting } from '@renderer/types'
|
||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||
import { Avatar, Button, Select, Tooltip } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { Info } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import SendMessageButton from '../home/Inputbar/SendMessageButton'
|
||||
import { SettingHelpLink, SettingTitle } from '../settings'
|
||||
import Artboard from './components/Artboard'
|
||||
import { DynamicFormRender } from './components/DynamicFormRender'
|
||||
import PaintingsList from './components/PaintingsList'
|
||||
import { DEFAULT_TOKENFLUX_PAINTING, type TokenFluxModel } from './config/tokenFluxConfig'
|
||||
import TokenFluxService from './utils/TokenFluxService'
|
||||
|
||||
const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
const [models, setModels] = useState<TokenFluxModel[]>([])
|
||||
const [selectedModel, setSelectedModel] = useState<TokenFluxModel | null>(null)
|
||||
const [formData, setFormData] = useState<Record<string, any>>({})
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null)
|
||||
const [spaceClickCount, setSpaceClickCount] = useState(0)
|
||||
const [isTranslating, setIsTranslating] = useState(false)
|
||||
|
||||
const { t, i18n } = useTranslation()
|
||||
const providers = useAllProviders()
|
||||
const { addPainting, removePainting, updatePainting, persistentData } = usePaintings()
|
||||
const tokenFluxPaintings = useMemo(() => persistentData.tokenFluxPaintings || [], [persistentData.tokenFluxPaintings])
|
||||
const [painting, setPainting] = useState<TokenFluxPainting>(
|
||||
tokenFluxPaintings[0] || { ...DEFAULT_TOKENFLUX_PAINTING, id: uuid() }
|
||||
)
|
||||
|
||||
const providerOptions = Options.map((option) => {
|
||||
const provider = providers.find((p) => p.id === option)
|
||||
return {
|
||||
label: t(`provider.${provider?.id}`),
|
||||
value: provider?.id
|
||||
}
|
||||
})
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const { generating } = useRuntime()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { autoTranslateWithSpace } = useSettings()
|
||||
const spaceClickTimer = useRef<NodeJS.Timeout>(null)
|
||||
const tokenfluxProvider = providers.find((p) => p.id === 'tokenflux')!
|
||||
const textareaRef = useRef<any>(null)
|
||||
const tokenFluxService = useMemo(
|
||||
() => new TokenFluxService(tokenfluxProvider.apiHost, tokenfluxProvider.apiKey),
|
||||
[tokenfluxProvider]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
tokenFluxService.fetchModels().then((models) => {
|
||||
setModels(models)
|
||||
if (models.length > 0) {
|
||||
setSelectedModel(models[0])
|
||||
}
|
||||
})
|
||||
}, [tokenFluxService])
|
||||
|
||||
const getNewPainting = useCallback(() => {
|
||||
return {
|
||||
...DEFAULT_TOKENFLUX_PAINTING,
|
||||
id: uuid(),
|
||||
model: selectedModel?.id || '',
|
||||
inputParams: {},
|
||||
generationId: undefined
|
||||
}
|
||||
}, [selectedModel])
|
||||
|
||||
const updatePaintingState = useCallback(
|
||||
(updates: Partial<TokenFluxPainting>) => {
|
||||
setPainting((prevPainting) => {
|
||||
const updatedPainting = { ...prevPainting, ...updates }
|
||||
updatePainting('tokenFluxPaintings', updatedPainting)
|
||||
return updatedPainting
|
||||
})
|
||||
},
|
||||
[updatePainting]
|
||||
)
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
if (error instanceof Error && error.name !== 'AbortError') {
|
||||
window.modal.error({
|
||||
content: getErrorMessage(error),
|
||||
centered: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleModelChange = (modelId: string) => {
|
||||
const model = models.find((m) => m.id === modelId)
|
||||
if (model) {
|
||||
setSelectedModel(model)
|
||||
setFormData({})
|
||||
updatePaintingState({ model: model.id, inputParams: {} })
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormFieldChange = (field: string, value: any) => {
|
||||
const newFormData = { ...formData, [field]: value }
|
||||
setFormData(newFormData)
|
||||
updatePaintingState({ inputParams: newFormData })
|
||||
}
|
||||
|
||||
const onGenerate = async () => {
|
||||
if (painting.files.length > 0) {
|
||||
const confirmed = await window.modal.confirm({
|
||||
content: t('paintings.regenerate.confirm'),
|
||||
centered: true
|
||||
})
|
||||
|
||||
if (!confirmed) return
|
||||
await FileManager.deleteFiles(painting.files)
|
||||
}
|
||||
|
||||
const prompt = textareaRef.current?.resizableTextArea?.textArea?.value || ''
|
||||
|
||||
if (!tokenfluxProvider.enabled) {
|
||||
window.modal.error({
|
||||
content: t('error.provider_disabled'),
|
||||
centered: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!tokenfluxProvider.apiKey) {
|
||||
window.modal.error({
|
||||
content: t('error.no_api_key'),
|
||||
centered: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedModel || !prompt) {
|
||||
window.modal.error({
|
||||
content: t('paintings.text_desc_required'),
|
||||
centered: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
setAbortController(controller)
|
||||
setIsLoading(true)
|
||||
dispatch(setGenerating(true))
|
||||
|
||||
try {
|
||||
const requestBody = {
|
||||
model: selectedModel.id,
|
||||
input: {
|
||||
prompt,
|
||||
...formData
|
||||
}
|
||||
}
|
||||
|
||||
const inputParams = { prompt, ...formData }
|
||||
updatePaintingState({
|
||||
model: selectedModel.id,
|
||||
prompt,
|
||||
status: 'processing',
|
||||
inputParams
|
||||
})
|
||||
|
||||
const result = await tokenFluxService.generateAndWait(requestBody, {
|
||||
signal: controller.signal,
|
||||
onStatusUpdate: (updates) => {
|
||||
updatePaintingState(updates)
|
||||
}
|
||||
})
|
||||
|
||||
if (result && result.images && result.images.length > 0) {
|
||||
const urls = result.images.map((img: { url: string }) => img.url)
|
||||
const validFiles = await tokenFluxService.downloadImages(urls)
|
||||
await FileManager.addFiles(validFiles)
|
||||
updatePaintingState({ files: validFiles, urls, status: 'succeeded' })
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
dispatch(setGenerating(false))
|
||||
setAbortController(null)
|
||||
} catch (error: unknown) {
|
||||
handleError(error)
|
||||
setIsLoading(false)
|
||||
dispatch(setGenerating(false))
|
||||
setAbortController(null)
|
||||
}
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
abortController?.abort()
|
||||
setIsLoading(false)
|
||||
dispatch(setGenerating(false))
|
||||
setAbortController(null)
|
||||
}
|
||||
|
||||
const nextImage = () => {
|
||||
setCurrentImageIndex((prev) => (prev + 1) % painting.files.length)
|
||||
}
|
||||
|
||||
const prevImage = () => {
|
||||
setCurrentImageIndex((prev) => (prev - 1 + painting.files.length) % painting.files.length)
|
||||
}
|
||||
|
||||
const handleAddPainting = () => {
|
||||
const newPainting = addPainting('tokenFluxPaintings', getNewPainting())
|
||||
updatePainting('tokenFluxPaintings', newPainting)
|
||||
setPainting(newPainting as TokenFluxPainting)
|
||||
return newPainting
|
||||
}
|
||||
|
||||
const onDeletePainting = (paintingToDelete: TokenFluxPainting) => {
|
||||
if (paintingToDelete.id === painting.id) {
|
||||
const currentIndex = tokenFluxPaintings.findIndex((p) => p.id === paintingToDelete.id)
|
||||
|
||||
if (currentIndex > 0) {
|
||||
setPainting(tokenFluxPaintings[currentIndex - 1])
|
||||
} else if (tokenFluxPaintings.length > 1) {
|
||||
setPainting(tokenFluxPaintings[1])
|
||||
}
|
||||
}
|
||||
|
||||
removePainting('tokenFluxPaintings', paintingToDelete)
|
||||
}
|
||||
|
||||
const translate = async () => {
|
||||
if (isTranslating) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!painting.prompt) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setIsTranslating(true)
|
||||
const translatedText = await translateText(painting.prompt, 'english')
|
||||
updatePaintingState({ prompt: translatedText })
|
||||
} catch (error) {
|
||||
console.error('Translation failed:', error)
|
||||
} finally {
|
||||
setIsTranslating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (autoTranslateWithSpace && event.key === ' ') {
|
||||
setSpaceClickCount((prev) => prev + 1)
|
||||
|
||||
if (spaceClickTimer.current) {
|
||||
clearTimeout(spaceClickTimer.current)
|
||||
}
|
||||
|
||||
spaceClickTimer.current = setTimeout(() => {
|
||||
setSpaceClickCount(0)
|
||||
}, 200)
|
||||
|
||||
if (spaceClickCount === 2) {
|
||||
setSpaceClickCount(0)
|
||||
setIsTranslating(true)
|
||||
translate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleProviderChange = (providerId: string) => {
|
||||
const routeName = location.pathname.split('/').pop()
|
||||
if (providerId !== routeName) {
|
||||
navigate('../' + providerId, { replace: true })
|
||||
}
|
||||
}
|
||||
|
||||
const onSelectPainting = (newPainting: TokenFluxPainting) => {
|
||||
if (generating) return
|
||||
setPainting(newPainting)
|
||||
setCurrentImageIndex(0)
|
||||
|
||||
// Set form data from painting's input params
|
||||
if (newPainting.inputParams) {
|
||||
// Filter out the prompt from inputParams since it's handled separately
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { prompt, ...formInputParams } = newPainting.inputParams
|
||||
setFormData(formInputParams)
|
||||
} else {
|
||||
setFormData({})
|
||||
}
|
||||
|
||||
// Set selected model if available
|
||||
if (newPainting.model) {
|
||||
const model = models.find((m) => m.id === newPainting.model)
|
||||
if (model) {
|
||||
setSelectedModel(model)
|
||||
}
|
||||
} else {
|
||||
setSelectedModel(null)
|
||||
}
|
||||
}
|
||||
|
||||
const readI18nContext = (property: Record<string, any>, key: string): string => {
|
||||
const lang = i18n.language.split('-')[0] // Get the base language code (e.g., 'en' from 'en-US')
|
||||
console.log('readI18nContext', { property, key, lang })
|
||||
return property[`${key}_${lang}`] || property[key]
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (tokenFluxPaintings.length === 0) {
|
||||
const newPainting = getNewPainting()
|
||||
addPainting('tokenFluxPaintings', newPainting)
|
||||
setPainting(newPainting)
|
||||
}
|
||||
}, [tokenFluxPaintings, addPainting, getNewPainting])
|
||||
|
||||
useEffect(() => {
|
||||
const timer = spaceClickTimer.current
|
||||
return () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (painting.status === 'processing' && painting.generationId) {
|
||||
tokenFluxService
|
||||
.pollGenerationResult(painting.generationId, {
|
||||
onStatusUpdate: (updates) => {
|
||||
console.log('Polling status update:', updates)
|
||||
updatePaintingState(updates)
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
if (result && result.images && result.images.length > 0) {
|
||||
const urls = result.images.map((img: { url: string }) => img.url)
|
||||
tokenFluxService.downloadImages(urls).then(async (validFiles) => {
|
||||
await FileManager.addFiles(validFiles)
|
||||
updatePaintingState({ files: validFiles, urls, status: 'succeeded' })
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Polling failed:', error)
|
||||
updatePaintingState({ status: 'failed' })
|
||||
})
|
||||
}
|
||||
}, [painting.generationId, painting.status, tokenFluxService, updatePaintingState])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('paintings.title')}</NavbarCenter>
|
||||
{isMac && (
|
||||
<NavbarRight style={{ justifyContent: 'flex-end' }}>
|
||||
<Button size="small" className="nodrag" icon={<PlusOutlined />} onClick={handleAddPainting}>
|
||||
{t('paintings.button.new.image')}
|
||||
</Button>
|
||||
</NavbarRight>
|
||||
)}
|
||||
</Navbar>
|
||||
<ContentContainer id="content-container">
|
||||
<LeftContainer>
|
||||
{/* Provider Section */}
|
||||
<ProviderTitleContainer>
|
||||
<SettingTitle style={{ marginBottom: 8 }}>{t('common.provider')}</SettingTitle>
|
||||
<SettingHelpLink target="_blank" href="https://tokenflux.ai">
|
||||
{t('paintings.learn_more')}
|
||||
<ProviderLogo shape="square" src={getProviderLogo('tokenflux')} size={16} style={{ marginLeft: 5 }} />
|
||||
</SettingHelpLink>
|
||||
</ProviderTitleContainer>
|
||||
|
||||
<Select
|
||||
value={providerOptions.find((p) => p.value === 'tokenflux')?.value}
|
||||
onChange={handleProviderChange}
|
||||
style={{ width: '100%' }}>
|
||||
{providerOptions.map((provider) => (
|
||||
<Select.Option value={provider.value} key={provider.value}>
|
||||
<SelectOptionContainer>
|
||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
||||
{provider.label}
|
||||
</SelectOptionContainer>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{/* Model & Pricing Section */}
|
||||
<SectionTitle
|
||||
style={{ marginBottom: 5, marginTop: 15, justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
{t('paintings.model_and_pricing')}
|
||||
{selectedModel && selectedModel.pricing && (
|
||||
<PricingContainer>
|
||||
<PricingBadge>
|
||||
{selectedModel.pricing.price} {selectedModel.pricing.currency}{' '}
|
||||
{selectedModel.pricing.unit > 1 ? t('paintings.per_images') : t('paintings.per_image')}
|
||||
</PricingBadge>
|
||||
</PricingContainer>
|
||||
)}
|
||||
</SectionTitle>
|
||||
<Select
|
||||
style={{ width: '100%', marginBottom: 12 }}
|
||||
value={selectedModel?.id}
|
||||
onChange={handleModelChange}
|
||||
placeholder={t('paintings.select_model')}>
|
||||
{Object.entries(
|
||||
models.reduce(
|
||||
(acc, model) => {
|
||||
const provider = model.model_provider || 'Other'
|
||||
if (!acc[provider]) {
|
||||
acc[provider] = []
|
||||
}
|
||||
acc[provider].push(model)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, typeof models>
|
||||
)
|
||||
).map(([provider, providerModels]) => (
|
||||
<Select.OptGroup key={provider} label={provider}>
|
||||
{providerModels.map((model) => (
|
||||
<Select.Option key={model.id} value={model.id}>
|
||||
<Tooltip title={model.description} placement="right">
|
||||
<ModelOptionContainer>
|
||||
<ModelName>{model.name}</ModelName>
|
||||
</ModelOptionContainer>
|
||||
</Tooltip>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select.OptGroup>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{/* Input Parameters Section */}
|
||||
{selectedModel && selectedModel.input_schema && (
|
||||
<>
|
||||
<SectionTitle style={{ marginBottom: 5, marginTop: 10 }}>{t('paintings.input_parameters')}</SectionTitle>
|
||||
<ParametersContainer>
|
||||
{Object.entries(selectedModel.input_schema.properties).map(([key, property]: [string, any]) => {
|
||||
if (key === 'prompt') return null // Skip prompt as it's handled separately
|
||||
|
||||
const isRequired = selectedModel.input_schema.required?.includes(key)
|
||||
|
||||
return (
|
||||
<ParameterField key={key}>
|
||||
<ParameterLabel>
|
||||
<ParameterName>
|
||||
{readI18nContext(property, 'title')}
|
||||
{isRequired && <RequiredIndicator> *</RequiredIndicator>}
|
||||
</ParameterName>
|
||||
{property.description && (
|
||||
<Tooltip title={readI18nContext(property, 'description')}>
|
||||
<InfoIcon />
|
||||
</Tooltip>
|
||||
)}
|
||||
</ParameterLabel>
|
||||
<DynamicFormRender
|
||||
schemaProperty={property}
|
||||
propertyName={key}
|
||||
value={formData[key]}
|
||||
onChange={handleFormFieldChange}
|
||||
/>
|
||||
</ParameterField>
|
||||
)
|
||||
})}
|
||||
</ParametersContainer>
|
||||
</>
|
||||
)}
|
||||
</LeftContainer>
|
||||
|
||||
<MainContainer>
|
||||
{/* Check if any form field contains an uploaded image */}
|
||||
{Object.keys(formData).some((key) => key.toLowerCase().includes('image') && formData[key]) ? (
|
||||
<ComparisonContainer>
|
||||
<ImageComparisonSection>
|
||||
<SectionLabel>{t('paintings.input_image')}</SectionLabel>
|
||||
<UploadedImageContainer>
|
||||
{Object.entries(formData).map(([key, value]) => {
|
||||
if (key.toLowerCase().includes('image') && value) {
|
||||
return (
|
||||
<ImageWrapper key={key}>
|
||||
<img
|
||||
src={value}
|
||||
alt={t('paintings.uploaded_input')}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '70vh',
|
||||
objectFit: 'contain',
|
||||
backgroundColor: 'var(--color-background-soft)'
|
||||
}}
|
||||
/>
|
||||
</ImageWrapper>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</UploadedImageContainer>
|
||||
</ImageComparisonSection>
|
||||
<ImageComparisonSection>
|
||||
<SectionLabel>{t('paintings.generated_image')}</SectionLabel>
|
||||
<Artboard
|
||||
painting={painting}
|
||||
isLoading={isLoading}
|
||||
currentImageIndex={currentImageIndex}
|
||||
onPrevImage={prevImage}
|
||||
onNextImage={nextImage}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</ImageComparisonSection>
|
||||
</ComparisonContainer>
|
||||
) : (
|
||||
<Artboard
|
||||
painting={painting}
|
||||
isLoading={isLoading}
|
||||
currentImageIndex={currentImageIndex}
|
||||
onPrevImage={prevImage}
|
||||
onNextImage={nextImage}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
)}
|
||||
<InputContainer>
|
||||
<Textarea
|
||||
ref={textareaRef}
|
||||
variant="borderless"
|
||||
disabled={isLoading}
|
||||
value={painting.prompt || ''}
|
||||
spellCheck={false}
|
||||
onChange={(e) => updatePaintingState({ prompt: e.target.value })}
|
||||
placeholder={isTranslating ? t('paintings.translating') : t('paintings.prompt_placeholder')}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarMenu>
|
||||
<TranslateButton
|
||||
text={textareaRef.current?.resizableTextArea?.textArea?.value}
|
||||
onTranslated={(translatedText) => updatePaintingState({ prompt: translatedText })}
|
||||
disabled={isLoading || isTranslating}
|
||||
isLoading={isTranslating}
|
||||
style={{ marginRight: 6, borderRadius: '50%' }}
|
||||
/>
|
||||
<SendMessageButton sendMessage={onGenerate} disabled={isLoading} />
|
||||
</ToolbarMenu>
|
||||
</Toolbar>
|
||||
</InputContainer>
|
||||
</MainContainer>
|
||||
|
||||
<PaintingsList
|
||||
namespace="tokenFluxPaintings"
|
||||
paintings={tokenFluxPaintings}
|
||||
selectedPainting={painting}
|
||||
onSelectPainting={onSelectPainting as any}
|
||||
onDeletePainting={onDeletePainting as any}
|
||||
onNewPainting={handleAddPainting}
|
||||
/>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const SectionTitle = styled.div`
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ModelOptionContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const ModelName = styled.div`
|
||||
color: var(--color-text);
|
||||
`
|
||||
|
||||
const PricingContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`
|
||||
|
||||
const PricingBadge = styled.div`
|
||||
background-color: var(--color-primary-bg);
|
||||
color: var(--color-primary);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 4px 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-primary-border);
|
||||
`
|
||||
|
||||
const ParametersContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
`
|
||||
|
||||
const ParameterField = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const ParameterLabel = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
`
|
||||
|
||||
const ParameterName = styled.span`
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
text-transform: capitalize;
|
||||
`
|
||||
|
||||
const RequiredIndicator = styled.span`
|
||||
color: var(--color-error);
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
background-color: var(--color-background);
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const LeftContainer = styled(Scrollbar)`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
background-color: var(--color-background);
|
||||
max-width: var(--assistants-width);
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: var(--color-background);
|
||||
`
|
||||
|
||||
const ComparisonContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
gap: 1px;
|
||||
`
|
||||
|
||||
const ImageComparisonSection = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: var(--color-background);
|
||||
&:first-child {
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
}
|
||||
`
|
||||
|
||||
const SectionLabel = styled.div`
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-background-soft);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const UploadedImageContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--color-background);
|
||||
`
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const InputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 95px;
|
||||
max-height: 95px;
|
||||
position: relative;
|
||||
border: 1px solid var(--color-border-soft);
|
||||
transition: all 0.3s ease;
|
||||
margin: 0 20px 15px 20px;
|
||||
border-radius: 10px;
|
||||
`
|
||||
|
||||
const Textarea = styled(TextArea)`
|
||||
padding: 10px;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
resize: none !important;
|
||||
overflow: auto;
|
||||
width: auto;
|
||||
`
|
||||
|
||||
const Toolbar = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
padding: 0 8px;
|
||||
padding-bottom: 0;
|
||||
height: 40px;
|
||||
`
|
||||
|
||||
const ToolbarMenu = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
`
|
||||
|
||||
const InfoIcon = styled(Info)`
|
||||
margin-left: 5px;
|
||||
cursor: help;
|
||||
color: var(--color-text-2);
|
||||
opacity: 0.6;
|
||||
width: 14px;
|
||||
height: 16px;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
const ProviderTitleContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
`
|
||||
|
||||
const SelectOptionContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
export default TokenFluxPage
|
||||
@@ -0,0 +1,213 @@
|
||||
import { CloseOutlined, LinkOutlined, RedoOutlined, UploadOutlined } from '@ant-design/icons'
|
||||
import { convertToBase64 } from '@renderer/utils'
|
||||
import { Button, Input, InputNumber, Select, Switch, Upload } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
interface DynamicFormRenderProps {
|
||||
schemaProperty: any
|
||||
propertyName: string
|
||||
value: any
|
||||
onChange: (field: string, value: any) => void
|
||||
}
|
||||
|
||||
export const DynamicFormRender: React.FC<DynamicFormRenderProps> = ({
|
||||
schemaProperty,
|
||||
propertyName,
|
||||
value,
|
||||
onChange
|
||||
}) => {
|
||||
const { type, enum: enumValues, description, default: defaultValue, format } = schemaProperty
|
||||
|
||||
const handleImageUpload = useCallback(
|
||||
async (
|
||||
propertyName: string,
|
||||
fileOrUrl: File | string,
|
||||
onChange: (field: string, value: any) => void
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (typeof fileOrUrl === 'string') {
|
||||
// Handle URL case - validate and set directly
|
||||
if (fileOrUrl.match(/^https?:\/\/.+\.(jpg|jpeg|png|gif|webp|svg)(\?.*)?$/i)) {
|
||||
onChange(propertyName, fileOrUrl)
|
||||
} else {
|
||||
window.message?.error('Invalid image URL format')
|
||||
}
|
||||
} else {
|
||||
// Handle File case - convert to base64
|
||||
const base64Image = await convertToBase64(fileOrUrl)
|
||||
if (typeof base64Image === 'string') {
|
||||
onChange(propertyName, base64Image)
|
||||
} else {
|
||||
console.error('Failed to convert image to base64')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing image:', error)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
if (type === 'string' && propertyName.toLowerCase().includes('image') && format === 'uri') {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||
<div style={{ display: 'flex', gap: '0' }}>
|
||||
<Input
|
||||
style={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderRight: 'none'
|
||||
}}
|
||||
value={value || defaultValue || ''}
|
||||
onChange={(e) => onChange(propertyName, e.target.value)}
|
||||
placeholder="Enter image URL or upload file"
|
||||
prefix={<LinkOutlined style={{ color: '#999' }} />}
|
||||
/>
|
||||
<Upload
|
||||
accept="image/*"
|
||||
showUploadList={false}
|
||||
beforeUpload={(file) => {
|
||||
handleImageUpload(propertyName, file, onChange)
|
||||
return false
|
||||
}}>
|
||||
<Button
|
||||
icon={<UploadOutlined />}
|
||||
title="Upload image file"
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
height: '32px'
|
||||
}}
|
||||
/>
|
||||
</Upload>
|
||||
</div>
|
||||
|
||||
{value && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
padding: '8px',
|
||||
backgroundColor: 'var(--color-fill-quaternary)',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid var(--color-border)'
|
||||
}}>
|
||||
<img
|
||||
src={value}
|
||||
alt="Image preview"
|
||||
style={{
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
objectFit: 'cover',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--color-border-secondary)',
|
||||
boxShadow: '0 1px 4px rgba(0, 0, 0, 0.1)',
|
||||
flexShrink: 0
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: '12px',
|
||||
color: 'var(--color-text-secondary)',
|
||||
minWidth: 0,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}>
|
||||
{value.startsWith('data:') ? 'Uploaded image' : 'Image URL'}
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
icon={<CloseOutlined />}
|
||||
onClick={() => onChange(propertyName, '')}
|
||||
title="Remove image"
|
||||
style={{ flexShrink: 0, minWidth: 'auto', padding: '0 8px' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'string' && enumValues) {
|
||||
return (
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
value={value || defaultValue}
|
||||
options={enumValues.map((val: string) => ({ label: val, value: val }))}
|
||||
onChange={(v) => onChange(propertyName, v)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'string') {
|
||||
if (propertyName.toLowerCase().includes('prompt') && propertyName !== 'prompt') {
|
||||
return (
|
||||
<TextArea
|
||||
value={value || defaultValue || ''}
|
||||
onChange={(e) => onChange(propertyName, e.target.value)}
|
||||
rows={3}
|
||||
placeholder={description}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Input
|
||||
value={value || defaultValue || ''}
|
||||
onChange={(e) => onChange(propertyName, e.target.value)}
|
||||
placeholder={description}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'integer' && propertyName === 'seed') {
|
||||
const generateRandomSeed = () => Math.floor(Math.random() * 1000000)
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<InputNumber
|
||||
style={{ flex: 1 }}
|
||||
value={value || defaultValue}
|
||||
onChange={(v) => onChange(propertyName, v)}
|
||||
step={1}
|
||||
min={schemaProperty.minimum}
|
||||
max={schemaProperty.maximum}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<RedoOutlined />}
|
||||
onClick={() => onChange(propertyName, generateRandomSeed())}
|
||||
title="Generate random seed"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'integer' || type === 'number') {
|
||||
const step = type === 'number' ? 0.1 : 1
|
||||
return (
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
value={value || defaultValue}
|
||||
onChange={(v) => onChange(propertyName, v)}
|
||||
step={step}
|
||||
min={schemaProperty.minimum}
|
||||
max={schemaProperty.maximum}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'boolean') {
|
||||
return (
|
||||
<Switch
|
||||
checked={value !== undefined ? value : defaultValue}
|
||||
onChange={(checked) => onChange(propertyName, checked)}
|
||||
style={{ width: '2px' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { TokenFluxPainting } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
|
||||
export interface TokenFluxModel {
|
||||
id: string
|
||||
name: string
|
||||
model_provider: string
|
||||
description: string
|
||||
tags: string[]
|
||||
pricing: any
|
||||
input_schema: {
|
||||
type: string
|
||||
properties: Record<string, any>
|
||||
required: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export const DEFAULT_TOKENFLUX_PAINTING: TokenFluxPainting = {
|
||||
id: uuid(),
|
||||
model: '',
|
||||
prompt: '',
|
||||
inputParams: {},
|
||||
status: 'starting',
|
||||
generationId: undefined,
|
||||
urls: [],
|
||||
files: []
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
import { CacheService } from '@renderer/services/CacheService'
|
||||
import { FileType, TokenFluxPainting } from '@renderer/types'
|
||||
|
||||
import type { TokenFluxModel } from '../config/tokenFluxConfig'
|
||||
|
||||
export interface TokenFluxGenerationRequest {
|
||||
model: string
|
||||
input: {
|
||||
prompt: string
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
export interface TokenFluxGenerationResponse {
|
||||
success: boolean
|
||||
data?: {
|
||||
id: string
|
||||
status: string
|
||||
images?: Array<{ url: string }>
|
||||
}
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface TokenFluxModelsResponse {
|
||||
success: boolean
|
||||
data?: TokenFluxModel[]
|
||||
message?: string
|
||||
}
|
||||
|
||||
export class TokenFluxService {
|
||||
private apiHost: string
|
||||
private apiKey: string
|
||||
|
||||
constructor(apiHost: string, apiKey: string) {
|
||||
this.apiHost = apiHost
|
||||
this.apiKey = apiKey
|
||||
}
|
||||
|
||||
private getHeaders(): Record<string, string> {
|
||||
return {
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
private async handleResponse<T>(response: Response): Promise<T> {
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(errorData.message || `HTTP ${response.status}: Request failed`)
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch available models from TokenFlux API
|
||||
*/
|
||||
async fetchModels(): Promise<TokenFluxModel[]> {
|
||||
const cacheKey = `tokenflux_models_${this.apiHost}`
|
||||
|
||||
// Check cache first
|
||||
const cachedModels = CacheService.get<TokenFluxModel[]>(cacheKey)
|
||||
if (cachedModels) {
|
||||
return cachedModels
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.apiHost}/v1/images/models`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.apiKey}`
|
||||
}
|
||||
})
|
||||
|
||||
const data: TokenFluxModelsResponse = await this.handleResponse(response)
|
||||
|
||||
if (!data.success || !data.data) {
|
||||
throw new Error('Failed to fetch models')
|
||||
}
|
||||
|
||||
// Cache for 60 minutes (3,600,000 milliseconds)
|
||||
CacheService.set(cacheKey, data.data, 60 * 60 * 1000)
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new image generation request
|
||||
*/
|
||||
async createGeneration(request: TokenFluxGenerationRequest, signal?: AbortSignal): Promise<string> {
|
||||
const response = await fetch(`${this.apiHost}/v1/images/generations`, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify(request),
|
||||
signal
|
||||
})
|
||||
|
||||
const data: TokenFluxGenerationResponse = await this.handleResponse(response)
|
||||
|
||||
if (!data.success || !data.data?.id) {
|
||||
throw new Error(data.message || 'Generation failed')
|
||||
}
|
||||
|
||||
return data.data.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status and result of a generation
|
||||
*/
|
||||
async getGenerationResult(generationId: string): Promise<TokenFluxGenerationResponse['data']> {
|
||||
const response = await fetch(`${this.apiHost}/v1/images/generations/${generationId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.apiKey}`
|
||||
}
|
||||
})
|
||||
|
||||
const data: TokenFluxGenerationResponse = await this.handleResponse(response)
|
||||
|
||||
if (!data.success || !data.data) {
|
||||
throw new Error('Invalid response from generation service')
|
||||
}
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll for generation result with automatic retry logic
|
||||
*/
|
||||
async pollGenerationResult(
|
||||
generationId: string,
|
||||
options: {
|
||||
onStatusUpdate?: (updates: Partial<TokenFluxPainting>) => void
|
||||
maxRetries?: number
|
||||
timeoutMs?: number
|
||||
intervalMs?: number
|
||||
} = {}
|
||||
): Promise<TokenFluxGenerationResponse['data']> {
|
||||
const {
|
||||
onStatusUpdate,
|
||||
maxRetries = 10,
|
||||
timeoutMs = 120000, // 2 minutes
|
||||
intervalMs = 2000
|
||||
} = options
|
||||
|
||||
const startTime = Date.now()
|
||||
let retryCount = 0
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const poll = async () => {
|
||||
try {
|
||||
// Check for timeout
|
||||
if (Date.now() - startTime > timeoutMs) {
|
||||
reject(new Error('Image generation timed out. Please try again.'))
|
||||
return
|
||||
}
|
||||
|
||||
const result = await this.getGenerationResult(generationId)
|
||||
|
||||
// Reset retry count on successful response
|
||||
retryCount = 0
|
||||
|
||||
if (result) {
|
||||
onStatusUpdate?.({ status: result.status as TokenFluxPainting['status'] })
|
||||
|
||||
if (result.status === 'succeeded') {
|
||||
resolve(result)
|
||||
return
|
||||
} else if (result.status === 'failed') {
|
||||
reject(new Error('Image generation failed'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Continue polling for other statuses (processing, queued, etc.)
|
||||
setTimeout(poll, intervalMs)
|
||||
} catch (error) {
|
||||
console.error('Polling error:', error)
|
||||
retryCount++
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
reject(new Error('Failed to check generation status after multiple attempts. Please try again.'))
|
||||
return
|
||||
}
|
||||
|
||||
// Retry after interval
|
||||
setTimeout(poll, intervalMs)
|
||||
}
|
||||
}
|
||||
|
||||
// Start polling
|
||||
poll()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create generation and poll for result in one call
|
||||
*/
|
||||
async generateAndWait(
|
||||
request: TokenFluxGenerationRequest,
|
||||
options: {
|
||||
onStatusUpdate?: (updates: Partial<TokenFluxPainting>) => void
|
||||
signal?: AbortSignal
|
||||
maxRetries?: number
|
||||
timeoutMs?: number
|
||||
intervalMs?: number
|
||||
} = {}
|
||||
): Promise<TokenFluxGenerationResponse['data']> {
|
||||
const { signal, onStatusUpdate, ...pollOptions } = options
|
||||
const generationId = await this.createGeneration(request, signal)
|
||||
if (onStatusUpdate) {
|
||||
onStatusUpdate({ generationId })
|
||||
}
|
||||
return this.pollGenerationResult(generationId, { ...pollOptions, onStatusUpdate })
|
||||
}
|
||||
|
||||
async downloadImages(urls: string[]) {
|
||||
const downloadedFiles = await Promise.all(
|
||||
urls.map(async (url) => {
|
||||
try {
|
||||
if (!url?.trim()) {
|
||||
console.error('Image URL is empty')
|
||||
window.message.warning({
|
||||
content: 'Image URL is empty',
|
||||
key: 'empty-url-warning'
|
||||
})
|
||||
return null
|
||||
}
|
||||
return await window.api.file.download(url)
|
||||
} catch (error) {
|
||||
console.error('Failed to download image:', error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return downloadedFiles.filter((file): file is FileType => file !== null)
|
||||
}
|
||||
}
|
||||
|
||||
export default TokenFluxService
|
||||
@@ -13,6 +13,8 @@ import { useTranslation } from 'react-i18next'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SettingDivider } from '..'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
updateAssistant: (assistant: Assistant) => void
|
||||
@@ -90,7 +92,8 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack mt={8} mb={8} alignItems="center" gap={4}>
|
||||
<SettingDivider />
|
||||
<HStack mb={8} alignItems="center" gap={4}>
|
||||
<Box style={{ fontWeight: 'bold' }}>{t('common.prompt')}</Box>
|
||||
<Tooltip title={t('agents.add.prompt.variables.tip')}>
|
||||
<QuestionCircleOutlined size={14} color="var(--color-text-2)" />
|
||||
@@ -139,7 +142,6 @@ const Container = styled.div`
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 5px;
|
||||
`
|
||||
|
||||
const EmojiButtonWrapper = styled.div`
|
||||
|
||||
+52
-50
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitle } from '..'
|
||||
import { SettingDivider, SettingRow, SettingTitle } from '..'
|
||||
|
||||
const { TextArea } = Input
|
||||
|
||||
@@ -79,52 +79,50 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
|
||||
const reversedPrompts = [...promptsList].reverse()
|
||||
|
||||
return (
|
||||
<SettingContainer style={{ padding: 0, background: '#0000' }}>
|
||||
<SettingGroup style={{ marginBottom: 0, padding: 0, border: 'none' }}>
|
||||
<SettingTitle>
|
||||
{t('assistants.settings.regular_phrases.title', 'Regular Prompts')}
|
||||
<Button type="text" icon={<PlusOutlined />} onClick={handleAdd} />
|
||||
</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<StyledPromptList>
|
||||
<DragableList
|
||||
list={reversedPrompts}
|
||||
onUpdate={(newPrompts) => handleUpdateOrder([...newPrompts].reverse())}
|
||||
style={{ paddingBottom: dragging ? '34px' : 0 }}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(prompt) => (
|
||||
<FileItem
|
||||
key={prompt.id}
|
||||
fileInfo={{
|
||||
name: prompt.title,
|
||||
ext: '.txt',
|
||||
extra: prompt.content,
|
||||
actions: (
|
||||
<Flex gap={4} style={{ opacity: 0.6 }}>
|
||||
<Button key="edit" type="text" icon={<EditOutlined />} onClick={() => handleEdit(prompt)} />
|
||||
<Popconfirm
|
||||
title={t('assistants.settings.regular_phrases.delete', 'Delete Prompt')}
|
||||
description={t(
|
||||
'assistants.settings.regular_phrases.deleteConfirm',
|
||||
'Are you sure to delete this prompt?'
|
||||
)}
|
||||
okText={t('common.confirm')}
|
||||
cancelText={t('common.cancel')}
|
||||
onConfirm={() => handleDelete(prompt.id)}
|
||||
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
|
||||
<Button key="delete" type="text" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
</Flex>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DragableList>
|
||||
</StyledPromptList>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
<Container>
|
||||
<SettingTitle>
|
||||
{t('assistants.settings.regular_phrases.title', 'Regular Prompts')}
|
||||
<Button type="text" icon={<PlusOutlined />} onClick={handleAdd} />
|
||||
</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<StyledPromptList>
|
||||
<DragableList
|
||||
list={reversedPrompts}
|
||||
onUpdate={(newPrompts) => handleUpdateOrder([...newPrompts].reverse())}
|
||||
style={{ paddingBottom: dragging ? '34px' : 0 }}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(prompt) => (
|
||||
<FileItem
|
||||
key={prompt.id}
|
||||
fileInfo={{
|
||||
name: prompt.title,
|
||||
ext: '.txt',
|
||||
extra: prompt.content,
|
||||
actions: (
|
||||
<Flex gap={4} style={{ opacity: 0.6 }}>
|
||||
<Button key="edit" type="text" icon={<EditOutlined />} onClick={() => handleEdit(prompt)} />
|
||||
<Popconfirm
|
||||
title={t('assistants.settings.regular_phrases.delete', 'Delete Prompt')}
|
||||
description={t(
|
||||
'assistants.settings.regular_phrases.deleteConfirm',
|
||||
'Are you sure to delete this prompt?'
|
||||
)}
|
||||
okText={t('common.confirm')}
|
||||
cancelText={t('common.cancel')}
|
||||
onConfirm={() => handleDelete(prompt.id)}
|
||||
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
|
||||
<Button key="delete" type="text" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
</Flex>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DragableList>
|
||||
</StyledPromptList>
|
||||
</SettingRow>
|
||||
|
||||
<Modal
|
||||
title={
|
||||
@@ -159,10 +157,16 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
|
||||
</div>
|
||||
</Space>
|
||||
</Modal>
|
||||
</SettingContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Label = styled.div`
|
||||
font-size: 14px;
|
||||
color: var(--color-text);
|
||||
@@ -171,8 +175,6 @@ const Label = styled.div`
|
||||
|
||||
const StyledPromptList = styled.div`
|
||||
width: 100%;
|
||||
height: calc(100vh - 162px); // Adjusted height to match other settings pages
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
@@ -55,7 +55,7 @@ import mime from 'mime'
|
||||
import OpenAI from 'openai'
|
||||
import { ChatCompletionContentPart, ChatCompletionMessageParam } from 'openai/resources/chat/completions'
|
||||
import { Stream } from 'openai/streaming'
|
||||
import { FileLike, toFile } from 'openai/uploads'
|
||||
import { toFile, Uploadable } from 'openai/uploads'
|
||||
|
||||
import { CompletionsParams } from '.'
|
||||
import BaseProvider from './BaseProvider'
|
||||
@@ -1052,7 +1052,7 @@ export abstract class BaseOpenAIProvider extends BaseProvider {
|
||||
const { signal } = abortController
|
||||
const content = getMainTextContent(lastUserMessage!)
|
||||
let response: OpenAI.Images.ImagesResponse | null = null
|
||||
let images: FileLike[] = []
|
||||
let images: Uploadable[] = []
|
||||
|
||||
try {
|
||||
if (lastUserMessage) {
|
||||
@@ -1084,7 +1084,7 @@ export abstract class BaseOpenAIProvider extends BaseProvider {
|
||||
return await toFile(bytes, fileName, { type: mimeType })
|
||||
})
|
||||
)
|
||||
images = images.concat(assistantImages.filter(Boolean) as FileLike[])
|
||||
images = images.concat(assistantImages.filter(Boolean) as Uploadable[])
|
||||
}
|
||||
|
||||
onChunk({
|
||||
|
||||
@@ -66,16 +66,6 @@ export const INITIAL_PROVIDERS: Provider[] = [
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://openrouter.ai/api/v1/',
|
||||
models: SYSTEM_MODELS.openrouter,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'ppio',
|
||||
name: 'PPIO',
|
||||
@@ -96,16 +86,6 @@ export const INITIAL_PROVIDERS: Provider[] = [
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'infini',
|
||||
name: 'Infini',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://cloud.infini-ai.com/maas',
|
||||
models: SYSTEM_MODELS.infini,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'qiniu',
|
||||
name: 'Qiniu',
|
||||
@@ -136,6 +116,16 @@ export const INITIAL_PROVIDERS: Provider[] = [
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'tokenflux',
|
||||
name: 'TokenFlux',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://tokenflux.ai',
|
||||
models: SYSTEM_MODELS.tokenflux,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'o3',
|
||||
name: 'O3',
|
||||
@@ -146,6 +136,16 @@ export const INITIAL_PROVIDERS: Provider[] = [
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://openrouter.ai/api/v1/',
|
||||
models: SYSTEM_MODELS.openrouter,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
@@ -298,6 +298,16 @@ export const INITIAL_PROVIDERS: Provider[] = [
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'infini',
|
||||
name: 'Infini',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://cloud.infini-ai.com/maas',
|
||||
models: SYSTEM_MODELS.infini,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'minimax',
|
||||
name: 'MiniMax',
|
||||
@@ -477,16 +487,6 @@ export const INITIAL_PROVIDERS: Provider[] = [
|
||||
models: SYSTEM_MODELS.voyageai,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: 'tokenflux',
|
||||
name: 'TokenFlux',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://tokenflux.ai',
|
||||
models: SYSTEM_MODELS.tokenflux,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -1475,7 +1475,23 @@ const migrateConfig = {
|
||||
},
|
||||
'110': (state: RootState) => {
|
||||
try {
|
||||
if (!state.preprocess) {
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === 'mistral') {
|
||||
provider.type = 'mistral'
|
||||
}
|
||||
})
|
||||
if (state.paintings && !state.paintings.tokenFluxPaintings) {
|
||||
state.paintings.tokenFluxPaintings = []
|
||||
}
|
||||
state.settings.showTokens = true
|
||||
return state
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
},
|
||||
'111': (state: RootState) => {
|
||||
try{
|
||||
if (!state.preprocess) {
|
||||
state.preprocess = {
|
||||
defaultProvider: '',
|
||||
providers: []
|
||||
@@ -1515,15 +1531,9 @@ const migrateConfig = {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === 'mistral') {
|
||||
provider.type = 'mistral'
|
||||
}
|
||||
})
|
||||
state.settings.showTokens = true
|
||||
return state
|
||||
} catch (error) {
|
||||
}catch(error) {
|
||||
console.error(error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ const initialState: PaintingsState = {
|
||||
remix: [],
|
||||
edit: [],
|
||||
upscale: [],
|
||||
DMXAPIPaintings: []
|
||||
DMXAPIPaintings: [],
|
||||
tokenFluxPaintings: []
|
||||
}
|
||||
|
||||
const paintingsSlice = createSlice({
|
||||
@@ -38,7 +39,13 @@ const paintingsSlice = createSlice({
|
||||
action: PayloadAction<{ namespace?: keyof PaintingsState; painting: PaintingAction }>
|
||||
) => {
|
||||
const { namespace = 'paintings', painting } = action.payload
|
||||
state[namespace] = state[namespace].map((c) => (c.id === painting.id ? painting : c))
|
||||
|
||||
const existingIndex = state[namespace].findIndex((c) => c.id === painting.id)
|
||||
if (existingIndex !== -1) {
|
||||
state[namespace] = state[namespace].map((c) => (c.id === painting.id ? painting : c))
|
||||
} else {
|
||||
console.error(`Painting with id ${painting.id} not found in ${namespace}`)
|
||||
}
|
||||
},
|
||||
updatePaintings: (
|
||||
state: PaintingsState,
|
||||
|
||||
@@ -271,7 +271,18 @@ export interface DmxapiPainting extends PaintingParams {
|
||||
autoCreate?: boolean
|
||||
}
|
||||
|
||||
export type PaintingAction = Partial<GeneratePainting & RemixPainting & EditPainting & ScalePainting> & PaintingParams
|
||||
export interface TokenFluxPainting extends PaintingParams {
|
||||
generationId?: string
|
||||
model?: string
|
||||
prompt?: string
|
||||
inputParams?: Record<string, any>
|
||||
status?: 'starting' | 'processing' | 'succeeded' | 'failed' | 'cancelled'
|
||||
}
|
||||
|
||||
export type PaintingAction = Partial<
|
||||
GeneratePainting & RemixPainting & EditPainting & ScalePainting & DmxapiPainting & TokenFluxPainting
|
||||
> &
|
||||
PaintingParams
|
||||
|
||||
export interface PaintingsState {
|
||||
paintings: Painting[]
|
||||
@@ -280,6 +291,7 @@ export interface PaintingsState {
|
||||
edit: Partial<EditPainting> & PaintingParams[]
|
||||
upscale: Partial<ScalePainting> & PaintingParams[]
|
||||
DMXAPIPaintings: DmxapiPainting[]
|
||||
tokenFluxPaintings: TokenFluxPainting[]
|
||||
}
|
||||
|
||||
export type MinAppType = {
|
||||
|
||||
@@ -147,10 +147,12 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
|
||||
}, [demo, isCompact, actionItems])
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language || navigator.language || defaultLanguage)
|
||||
}, [language])
|
||||
!demo && i18n.changeLanguage(language || navigator.language || defaultLanguage)
|
||||
}, [language, demo])
|
||||
|
||||
useEffect(() => {
|
||||
if (demo) return
|
||||
|
||||
let customCssElement = document.getElementById('user-defined-custom-css') as HTMLStyleElement
|
||||
if (customCssElement) {
|
||||
customCssElement.remove()
|
||||
@@ -164,7 +166,7 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => {
|
||||
customCssElement.textContent = newCustomCss
|
||||
document.head.appendChild(customCssElement)
|
||||
}
|
||||
}, [customCss])
|
||||
}, [customCss, demo])
|
||||
|
||||
const onHideCleanUp = () => {
|
||||
setCopyIconStatus('normal')
|
||||
@@ -265,6 +267,7 @@ const LogoWrapper = styled.div`
|
||||
justify-content: center;
|
||||
-webkit-app-region: drag;
|
||||
margin-left: 5px;
|
||||
background-color: transparent;
|
||||
`
|
||||
|
||||
const Logo = styled(Avatar)`
|
||||
@@ -295,6 +298,7 @@ const ActionWrapper = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 3px;
|
||||
background-color: transparent;
|
||||
`
|
||||
const ActionButton = styled.div`
|
||||
display: flex;
|
||||
@@ -302,6 +306,7 @@ const ActionButton = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 2px;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
@@ -309,6 +314,7 @@ const ActionButton = styled.div`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--color-selection-toolbar-text);
|
||||
background-color: transparent;
|
||||
}
|
||||
.btn-title {
|
||||
color: var(--color-selection-toolbar-text);
|
||||
@@ -329,10 +335,10 @@ const ActionIcon = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* margin-right: 3px; */
|
||||
position: relative;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: transparent;
|
||||
|
||||
.btn-icon {
|
||||
position: absolute;
|
||||
@@ -416,6 +422,7 @@ const ActionTitle = styled.span`
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-left: 3px;
|
||||
background-color: transparent;
|
||||
`
|
||||
|
||||
export default SelectionToolbar
|
||||
|
||||
@@ -2990,6 +2990,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mapbox/node-pre-gyp@npm:^1.0.0":
|
||||
version: 1.0.11
|
||||
resolution: "@mapbox/node-pre-gyp@npm:1.0.11"
|
||||
dependencies:
|
||||
detect-libc: "npm:^2.0.0"
|
||||
https-proxy-agent: "npm:^5.0.0"
|
||||
make-dir: "npm:^3.1.0"
|
||||
node-fetch: "npm:^2.6.7"
|
||||
nopt: "npm:^5.0.0"
|
||||
npmlog: "npm:^5.0.1"
|
||||
rimraf: "npm:^3.0.2"
|
||||
semver: "npm:^7.3.5"
|
||||
tar: "npm:^6.1.11"
|
||||
bin:
|
||||
node-pre-gyp: bin/node-pre-gyp
|
||||
checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@marijn/find-cluster-break@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "@marijn/find-cluster-break@npm:1.0.2"
|
||||
@@ -5664,7 +5683,7 @@ __metadata:
|
||||
node-stream-zip: "npm:^1.15.0"
|
||||
npx-scope-finder: "npm:^1.2.0"
|
||||
officeparser: "npm:^4.1.1"
|
||||
openai: "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch"
|
||||
openai: "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch"
|
||||
os-proxy-config: "npm:^1.1.2"
|
||||
p-queue: "npm:^8.1.0"
|
||||
pdf-to-img: "npm:^4.4.0"
|
||||
@@ -5714,7 +5733,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"abbrev@npm:^1.0.0":
|
||||
"abbrev@npm:1, abbrev@npm:^1.0.0":
|
||||
version: 1.1.1
|
||||
resolution: "abbrev@npm:1.1.1"
|
||||
checksum: 10c0/3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6
|
||||
@@ -6069,6 +6088,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aproba@npm:^1.0.3 || ^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "aproba@npm:2.0.0"
|
||||
checksum: 10c0/d06e26384a8f6245d8c8896e138c0388824e259a329e0c9f196b4fa533c82502a6fd449586e3604950a0c42921832a458bb3aa0aa9f0ba449cfd4f50fd0d09b5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2":
|
||||
version: 5.0.2
|
||||
resolution: "archiver-utils@npm:5.0.2"
|
||||
@@ -6099,6 +6125,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"are-we-there-yet@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "are-we-there-yet@npm:2.0.0"
|
||||
dependencies:
|
||||
delegates: "npm:^1.0.0"
|
||||
readable-stream: "npm:^3.6.0"
|
||||
checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"are-we-there-yet@npm:~1.1.2":
|
||||
version: 1.1.7
|
||||
resolution: "are-we-there-yet@npm:1.1.7"
|
||||
@@ -6692,6 +6728,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"canvas@npm:^2.11.2":
|
||||
version: 2.11.2
|
||||
resolution: "canvas@npm:2.11.2"
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp": "npm:^1.0.0"
|
||||
nan: "npm:^2.17.0"
|
||||
node-gyp: "npm:latest"
|
||||
simple-get: "npm:^3.0.3"
|
||||
checksum: 10c0/943368798ad1b66b18633aa34b6181e1038dac5433fc9727cd07be35f0a633f572b60d9edb95f5ff90b6a9128e86d5312035f91a2934101c73185b15d906230a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ccount@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "ccount@npm:1.1.0"
|
||||
@@ -7052,6 +7100,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-support@npm:^1.1.2":
|
||||
version: 1.1.3
|
||||
resolution: "color-support@npm:1.1.3"
|
||||
bin:
|
||||
color-support: bin.js
|
||||
checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "color@npm:5.0.0"
|
||||
@@ -7203,7 +7260,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"console-control-strings@npm:^1.0.0, console-control-strings@npm:~1.1.0":
|
||||
"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0, console-control-strings@npm:~1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "console-control-strings@npm:1.1.0"
|
||||
checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50
|
||||
@@ -10022,6 +10079,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gauge@npm:^3.0.0":
|
||||
version: 3.0.2
|
||||
resolution: "gauge@npm:3.0.2"
|
||||
dependencies:
|
||||
aproba: "npm:^1.0.3 || ^2.0.0"
|
||||
color-support: "npm:^1.1.2"
|
||||
console-control-strings: "npm:^1.0.0"
|
||||
has-unicode: "npm:^2.0.1"
|
||||
object-assign: "npm:^4.1.1"
|
||||
signal-exit: "npm:^3.0.0"
|
||||
string-width: "npm:^4.2.3"
|
||||
strip-ansi: "npm:^6.0.1"
|
||||
wide-align: "npm:^1.1.2"
|
||||
checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gauge@npm:~2.7.3":
|
||||
version: 2.7.4
|
||||
resolution: "gauge@npm:2.7.4"
|
||||
@@ -10404,7 +10478,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-unicode@npm:^2.0.0":
|
||||
"has-unicode@npm:^2.0.0, has-unicode@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "has-unicode@npm:2.0.1"
|
||||
checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c
|
||||
@@ -12146,6 +12220,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"make-dir@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "make-dir@npm:3.1.0"
|
||||
dependencies:
|
||||
semver: "npm:^6.0.0"
|
||||
checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"make-dir@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "make-dir@npm:4.0.0"
|
||||
@@ -13631,6 +13714,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nan@npm:^2.17.0":
|
||||
version: 2.22.2
|
||||
resolution: "nan@npm:2.22.2"
|
||||
dependencies:
|
||||
node-gyp: "npm:latest"
|
||||
checksum: 10c0/971f963b8120631880fa47a389c71b00cadc1c1b00ef8f147782a3f4387d4fc8195d0695911272d57438c11562fb27b24c4ae5f8c05d5e4eeb4478ba51bb73c5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nanoid@npm:^3.3.7, nanoid@npm:^3.3.8":
|
||||
version: 3.3.11
|
||||
resolution: "nanoid@npm:3.3.11"
|
||||
@@ -13859,6 +13951,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nopt@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "nopt@npm:5.0.0"
|
||||
dependencies:
|
||||
abbrev: "npm:1"
|
||||
bin:
|
||||
nopt: bin/nopt.js
|
||||
checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nopt@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "nopt@npm:6.0.0"
|
||||
@@ -13923,6 +14026,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"npmlog@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "npmlog@npm:5.0.1"
|
||||
dependencies:
|
||||
are-we-there-yet: "npm:^2.0.0"
|
||||
console-control-strings: "npm:^1.1.0"
|
||||
gauge: "npm:^3.0.0"
|
||||
set-blocking: "npm:^2.0.0"
|
||||
checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"npx-scope-finder@npm:^1.2.0":
|
||||
version: 1.3.0
|
||||
resolution: "npx-scope-finder@npm:1.3.0"
|
||||
@@ -13944,7 +14059,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0":
|
||||
"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414
|
||||
@@ -14080,17 +14195,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openai@npm:4.96.0":
|
||||
version: 4.96.0
|
||||
resolution: "openai@npm:4.96.0"
|
||||
dependencies:
|
||||
"@types/node": "npm:^18.11.18"
|
||||
"@types/node-fetch": "npm:^2.6.4"
|
||||
abort-controller: "npm:^3.0.0"
|
||||
agentkeepalive: "npm:^4.2.1"
|
||||
form-data-encoder: "npm:1.7.2"
|
||||
formdata-node: "npm:^4.3.2"
|
||||
node-fetch: "npm:^2.6.7"
|
||||
"openai@npm:5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "openai@npm:5.1.0"
|
||||
peerDependencies:
|
||||
ws: ^8.18.0
|
||||
zod: ^3.23.8
|
||||
@@ -14101,21 +14208,13 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
openai: bin/cli
|
||||
checksum: 10c0/d4c3fa76374730c856f774e07f375b51041b8e8429ae2cbd8605b168bf81673017f5dd1c0e42419ca54d8d3fd7cd93d57830d6bc6b9dcd317e70109018d599ea
|
||||
checksum: 10c0/d5882c95f95bfc4127ccbe494d298f43fe56cd3a9fd5711d1f02a040cfa6cdcc1e706ffe05f3d428421ec4caa526fe1b5ff50e1849dbfb3016d289853262ea3d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openai@patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch":
|
||||
version: 4.96.0
|
||||
resolution: "openai@patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch::version=4.96.0&hash=6bc976"
|
||||
dependencies:
|
||||
"@types/node": "npm:^18.11.18"
|
||||
"@types/node-fetch": "npm:^2.6.4"
|
||||
abort-controller: "npm:^3.0.0"
|
||||
agentkeepalive: "npm:^4.2.1"
|
||||
form-data-encoder: "npm:1.7.2"
|
||||
formdata-node: "npm:^4.3.2"
|
||||
node-fetch: "npm:^2.6.7"
|
||||
"openai@patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch":
|
||||
version: 5.1.0
|
||||
resolution: "openai@patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch::version=5.1.0&hash=cf4b11"
|
||||
peerDependencies:
|
||||
ws: ^8.18.0
|
||||
zod: ^3.23.8
|
||||
@@ -14126,7 +14225,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
openai: bin/cli
|
||||
checksum: 10c0/e50e4b9b60e94fadaca541cf2c36a12c55221555dd2ce977738e13978b7187504263f2e31b4641f2b6e70fce562b4e1fa2affd68caeca21248ddfa8847eeb003
|
||||
checksum: 10c0/26abab8311c5e130759d8b2a939ac408872d808c8e8b8f6a7bb5c85f2df0a66d61aece3af783dbf04a2aa401481cf20a6eddcb777b545b387f66220a6a6d25d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -15825,7 +15924,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0":
|
||||
"readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0":
|
||||
version: 3.6.2
|
||||
resolution: "readable-stream@npm:3.6.2"
|
||||
dependencies:
|
||||
@@ -16569,7 +16668,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^6.2.0, semver@npm:^6.3.1":
|
||||
"semver@npm:^6.0.0, semver@npm:^6.2.0, semver@npm:^6.3.1":
|
||||
version: 6.3.1
|
||||
resolution: "semver@npm:6.3.1"
|
||||
bin:
|
||||
@@ -16627,7 +16726,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"set-blocking@npm:~2.0.0":
|
||||
"set-blocking@npm:^2.0.0, set-blocking@npm:~2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "set-blocking@npm:2.0.0"
|
||||
checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454
|
||||
@@ -18687,7 +18786,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wide-align@npm:^1.1.0":
|
||||
"wide-align@npm:^1.1.0, wide-align@npm:^1.1.2":
|
||||
version: 1.1.5
|
||||
resolution: "wide-align@npm:1.1.5"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user