@rimori/client 2.5.50 → 2.5.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -129,12 +129,17 @@ export declare class AIModule {
|
|
|
129
129
|
* @param params.cache Whether to cache the result by prompt hash (default: true).
|
|
130
130
|
* @param params.aspectRatio Shape + size preset for the image (default: 'square_medium').
|
|
131
131
|
* `small` ≈ 200px on the longest edge, `medium` ≈ 512px, `big` keeps native HD resolution.
|
|
132
|
+
* @param params.bypassCache Force a fresh generation even if a cached image exists for this
|
|
133
|
+
* prompt, and overwrite that cache entry with the new result — use for a "regenerate, this
|
|
134
|
+
* looks bad" action so later callers get the improved image too. Unlike `cache: false`
|
|
135
|
+
* (which never reads OR writes the shared cache), this still writes it.
|
|
132
136
|
* @returns `{ url, cached }` where `url` is a stored CDN URL.
|
|
133
137
|
*/
|
|
134
138
|
getImage(params: {
|
|
135
139
|
prompt: string;
|
|
136
140
|
cache?: boolean;
|
|
137
141
|
aspectRatio?: ImageAspectRatio;
|
|
142
|
+
bypassCache?: boolean;
|
|
138
143
|
}): Promise<{
|
|
139
144
|
url: string;
|
|
140
145
|
cached: boolean;
|
|
@@ -127,16 +127,21 @@ export class AIModule {
|
|
|
127
127
|
* @param params.cache Whether to cache the result by prompt hash (default: true).
|
|
128
128
|
* @param params.aspectRatio Shape + size preset for the image (default: 'square_medium').
|
|
129
129
|
* `small` ≈ 200px on the longest edge, `medium` ≈ 512px, `big` keeps native HD resolution.
|
|
130
|
+
* @param params.bypassCache Force a fresh generation even if a cached image exists for this
|
|
131
|
+
* prompt, and overwrite that cache entry with the new result — use for a "regenerate, this
|
|
132
|
+
* looks bad" action so later callers get the improved image too. Unlike `cache: false`
|
|
133
|
+
* (which never reads OR writes the shared cache), this still writes it.
|
|
130
134
|
* @returns `{ url, cached }` where `url` is a stored CDN URL.
|
|
131
135
|
*/
|
|
132
136
|
async getImage(params) {
|
|
133
|
-
const { prompt, cache = true, aspectRatio = 'square_medium' } = params;
|
|
137
|
+
const { prompt, cache = true, aspectRatio = 'square_medium', bypassCache = false } = params;
|
|
134
138
|
const response = await this.controller.fetchBackend('/ai/image', {
|
|
135
139
|
method: 'POST',
|
|
136
140
|
body: JSON.stringify({
|
|
137
141
|
prompt,
|
|
138
142
|
cache,
|
|
139
143
|
aspectRatio,
|
|
144
|
+
bypassCache,
|
|
140
145
|
session_token_id: this.sessionTokenId ?? undefined,
|
|
141
146
|
}),
|
|
142
147
|
});
|
|
@@ -2,6 +2,7 @@ import { ObjectTool } from '../../fromRimori/PluginTypes';
|
|
|
2
2
|
import { SupabaseClient } from '../CommunicationHandler';
|
|
3
3
|
import { RimoriClient } from '../RimoriClient';
|
|
4
4
|
import { LanguageLevel } from '../../utils/difficultyConverter';
|
|
5
|
+
import { OnStreamedObjectResult } from './AIModule';
|
|
5
6
|
export type SharedContent<T> = BasicSharedContent & T;
|
|
6
7
|
export type ContentStatus = 'featured' | 'community' | 'unverified';
|
|
7
8
|
export type SharedContentSkillType = 'grammar' | 'reading' | 'writing' | 'speaking' | 'listening' | 'understanding';
|
|
@@ -43,6 +44,11 @@ export declare class SharedContentController {
|
|
|
43
44
|
* @param params.skipDbSave - If true, don't save generated content to database
|
|
44
45
|
* @param params.isPrivate - If true, content is guild-specific
|
|
45
46
|
* @param params.ignoreSkillLevel - If true, don't filter by skill level or add skill level guidance to AI instructions
|
|
47
|
+
* @param params.onResult - When provided, the request streams: this fires with each partial
|
|
48
|
+
* object as the LLM output arrives (e.g. a long markdown field can render progressively),
|
|
49
|
+
* then once more with the final object and `isLoading: false`. Only fires for an actual
|
|
50
|
+
* generation — a cache hit against existing content resolves the returned promise directly
|
|
51
|
+
* without ever calling it.
|
|
46
52
|
* @returns Existing or newly generated shared content item
|
|
47
53
|
*/
|
|
48
54
|
getNew<T>(params: {
|
|
@@ -58,7 +64,14 @@ export declare class SharedContentController {
|
|
|
58
64
|
skipDbSave?: boolean;
|
|
59
65
|
isPrivate?: boolean;
|
|
60
66
|
ignoreSkillLevel?: boolean;
|
|
67
|
+
onResult?: OnStreamedObjectResult<Partial<SharedContent<T>>>;
|
|
61
68
|
}): Promise<SharedContent<T>>;
|
|
69
|
+
/**
|
|
70
|
+
* SSE variant of `getNew` — same request/response shape, but reads the response as a stream of
|
|
71
|
+
* `data: {...}\n\n` frames (mirrors the framing `AIModule.getStreamedObject` parses off
|
|
72
|
+
* `/ai/llm`) and calls `onResult` with each partial object, then the final one.
|
|
73
|
+
*/
|
|
74
|
+
private getNewStreamed;
|
|
62
75
|
/**
|
|
63
76
|
* Search for shared content by topic using RAG (semantic similarity).
|
|
64
77
|
* Returns the first matching content that hasn't been completed by the user.
|
|
@@ -20,31 +20,89 @@ export class SharedContentController {
|
|
|
20
20
|
* @param params.skipDbSave - If true, don't save generated content to database
|
|
21
21
|
* @param params.isPrivate - If true, content is guild-specific
|
|
22
22
|
* @param params.ignoreSkillLevel - If true, don't filter by skill level or add skill level guidance to AI instructions
|
|
23
|
+
* @param params.onResult - When provided, the request streams: this fires with each partial
|
|
24
|
+
* object as the LLM output arrives (e.g. a long markdown field can render progressively),
|
|
25
|
+
* then once more with the final object and `isLoading: false`. Only fires for an actual
|
|
26
|
+
* generation — a cache hit against existing content resolves the returned promise directly
|
|
27
|
+
* without ever calling it.
|
|
23
28
|
* @returns Existing or newly generated shared content item
|
|
24
29
|
*/
|
|
25
30
|
async getNew(params) {
|
|
31
|
+
const requestBody = {
|
|
32
|
+
tableName: params.table,
|
|
33
|
+
skillType: params.skillType,
|
|
34
|
+
placeholders: params.placeholders,
|
|
35
|
+
filter: params.filter,
|
|
36
|
+
customFields: params.customFields,
|
|
37
|
+
tool: params.tool,
|
|
38
|
+
options: {
|
|
39
|
+
skipDbSave: params.skipDbSave,
|
|
40
|
+
isPrivate: params.isPrivate,
|
|
41
|
+
ignoreSkillLevel: params.ignoreSkillLevel,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
if (params.onResult) {
|
|
45
|
+
return this.getNewStreamed(requestBody, params.onResult);
|
|
46
|
+
}
|
|
26
47
|
// Generate new content via backend endpoint
|
|
27
48
|
const response = await this.rimoriClient.runtime.fetchBackend('/shared-content/generate', {
|
|
28
49
|
method: 'POST',
|
|
29
|
-
body: JSON.stringify(
|
|
30
|
-
tableName: params.table,
|
|
31
|
-
skillType: params.skillType,
|
|
32
|
-
placeholders: params.placeholders,
|
|
33
|
-
filter: params.filter,
|
|
34
|
-
customFields: params.customFields,
|
|
35
|
-
tool: params.tool,
|
|
36
|
-
options: {
|
|
37
|
-
skipDbSave: params.skipDbSave,
|
|
38
|
-
isPrivate: params.isPrivate,
|
|
39
|
-
ignoreSkillLevel: params.ignoreSkillLevel,
|
|
40
|
-
},
|
|
41
|
-
}),
|
|
50
|
+
body: JSON.stringify(requestBody),
|
|
42
51
|
});
|
|
43
52
|
if (!response.ok) {
|
|
44
53
|
throw new Error(`Failed to generate shared content: ${response.statusText}`);
|
|
45
54
|
}
|
|
46
55
|
return await response.json();
|
|
47
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* SSE variant of `getNew` — same request/response shape, but reads the response as a stream of
|
|
59
|
+
* `data: {...}\n\n` frames (mirrors the framing `AIModule.getStreamedObject` parses off
|
|
60
|
+
* `/ai/llm`) and calls `onResult` with each partial object, then the final one.
|
|
61
|
+
*/
|
|
62
|
+
async getNewStreamed(requestBody, onResult) {
|
|
63
|
+
const response = await this.rimoriClient.runtime.fetchBackend('/shared-content/generate', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
body: JSON.stringify({ ...requestBody, stream: true }),
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new Error(`Failed to generate shared content: ${response.statusText}`);
|
|
69
|
+
}
|
|
70
|
+
if (!response.body) {
|
|
71
|
+
throw new Error('No response body.');
|
|
72
|
+
}
|
|
73
|
+
const reader = response.body.getReader();
|
|
74
|
+
const decoder = new TextDecoder('utf-8');
|
|
75
|
+
let currentObject = {};
|
|
76
|
+
// See AIModule.streamObject — a chunk boundary can split a line mid-way, so an incomplete
|
|
77
|
+
// trailing line is buffered and prepended to the next chunk rather than parsed as-is.
|
|
78
|
+
let lineBuffer = '';
|
|
79
|
+
while (true) {
|
|
80
|
+
const { value, done } = await reader.read();
|
|
81
|
+
if (done)
|
|
82
|
+
break;
|
|
83
|
+
if (!value)
|
|
84
|
+
continue;
|
|
85
|
+
const combined = lineBuffer + decoder.decode(value, { stream: true });
|
|
86
|
+
const parts = combined.split('\n');
|
|
87
|
+
lineBuffer = parts.pop() ?? '';
|
|
88
|
+
for (const line of parts.filter((l) => l.trim())) {
|
|
89
|
+
if (line.startsWith('error:')) {
|
|
90
|
+
throw new Error(JSON.parse(line.slice(6).trim()).message ?? 'Error generating shared content');
|
|
91
|
+
}
|
|
92
|
+
if (!line.startsWith('data:'))
|
|
93
|
+
continue;
|
|
94
|
+
const dataStr = line.slice(5).trim();
|
|
95
|
+
if (dataStr === '[DONE]') {
|
|
96
|
+
onResult(currentObject, false);
|
|
97
|
+
return currentObject;
|
|
98
|
+
}
|
|
99
|
+
currentObject = JSON.parse(dataStr);
|
|
100
|
+
onResult(currentObject, true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
onResult(currentObject, false);
|
|
104
|
+
return currentObject;
|
|
105
|
+
}
|
|
48
106
|
/**
|
|
49
107
|
* Search for shared content by topic using RAG (semantic similarity).
|
|
50
108
|
* Returns the first matching content that hasn't been completed by the user.
|