@rizean/poe-canvas-utils
v0.3.1
Published
Utility hooks for Poe Canvas integration and logging.
Maintainers
Readme
Poe.com Canvas Utils
Utility hooks and functions for building applications on the Poe platform, particularly for Poe Canvas Apps. This library provides tools for AI interaction, client-side logging, functional error handling, text filtering, and data persistence via file download/upload.
Features
usePoeAi: A React hook to interact with a Poe AI bot via the Poe Embed API, supporting streaming, attachments, and a robust simulation mode for development.useLogger: A React hook for client-side logging with configurable levels and in-memory log storage, useful for debugging within the Poe Canvas environment.tryCatchSync&tryCatchAsync: Utility functions to wrap synchronous or asynchronous operations for functional error handling, returning aResulttuple ([data, null]or[null, error]).applyGeminiThinkingFilter: A utility function to clean up "Thinking..." artifacts from Gemini AI model responses.saveDataToFile&loadDataFromFile: Utilities to allow users to save application data to a JSON file and load it back, with support for data versioning, migration, and validation.- Poe Types: Comprehensive TypeScript definitions for the Poe Embed API (
window.Poe).
Installation
Replace @your-npm-username with the actual package name if published, or use a local path for local development. Assuming it's @rizean/poe-canvas-utils as per the project name:
Using pnpm:
pnpm add @rizean/poe-canvas-utilsUsing npm:
npm install @rizean/poe-canvas-utilsUsing yarn:
yarn add @rizean/poe-canvas-utilsUsage Examples
1. usePoeAi - Interacting with Poe Bots
This hook simplifies communication with Poe bots.
// MyPoeChatComponent.tsx
import React, { useState, useCallback } from 'react';
import { usePoeAi, type RequestState, type PoeMessage } from '@rizean/poe-canvas-utils';
function MyPoeChatComponent() {
const [sendToPoe] = usePoeAi({
// simulation: true, // Enable for development without Poe API
// logger: console, // Optional: use your own logger or console
});
const [isLoading, setIsLoading] = useState(false);
const [aiResponse, setAiResponse] = useState<PoeMessage | null>(null);
const [error, setError] = useState<string | null>(null);
const handleSubmitPrompt = useCallback((prompt: string) => {
setIsLoading(true);
setAiResponse(null);
setError(null);
const handlePoeUpdate = (state: RequestState) => {
console.log('Poe AI State Update:', state);
if (state.status === 'complete') {
setIsLoading(false);
if (state.responses && state.responses.length > 0) {
setAiResponse(state.responses[state.responses.length -1]); // Get the last message
}
} else if (state.status === 'error') {
setIsLoading(false);
setError(state.error);
} else if (state.status === 'incomplete') {
// Handle streaming updates if stream: true is used
if (state.responses && state.responses.length > 0) {
setAiResponse(state.responses[state.responses.length -1]);
}
}
};
// Example: Ensure window.Poe is available or simulation is enabled.
// The bot name (e.g., "@Gemini-2.5-Pro-Exp", "@Gemini-2.5-Flash") must be part of the prompt
// if the bot requires it.
sendToPoe(`@Gemini-2.5-Pro-Exp ${prompt}`, handlePoeUpdate, {
// stream: true, // Optional: for streaming responses
// openChat: false, // Optional: to prevent opening Poe chat UI
});
}, [sendToPoe]);
// Basic form for input
const [inputValue, setInputValue] = useState('');
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (inputValue.trim()) {
handleSubmitPrompt(inputValue.trim());
setInputValue('');
}
};
return (
<div>
<form onSubmit={handleFormSubmit}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Ask a Poe bot (e.g., @Gemini-2.5-Pro-Exp What is React?)"
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Thinking...' : 'Send'}
</button>
</form>
{aiResponse && (
<div>
<h4>AI Response:</h4>
<p><strong>Sender:</strong> {aiResponse.senderId}</p>
<p><strong>Content:</strong></p>
<pre>{aiResponse.content}</pre>
{aiResponse.attachments && aiResponse.attachments.length > 0 && (
<div>
<strong>Attachments:</strong>
<ul>
{aiResponse.attachments.map(att => (
<li key={att.attachmentId}><a href={att.url} target="_blank" rel="noopener noreferrer">{att.name}</a></li>
))}
</ul>
</div>
)}
</div>
)}
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
</div>
);
}
export default MyPoeChatComponent;2. usePoeAiTextGenerator - Interacting with Poe AI Text Generator Models
This hook is a specialized version of usePoeAi tailored for text generation use cases. It always streams responses and simplifies handling of text content, with optional parsing.
// MyTextGeneratorComponent.tsx
import React, { useState, useCallback } from 'react';
import {
usePoeAiTextGenerator,
type TextRequestState,
type TextRequestCallback,
type Result // For custom parser
} from '@rizean/poe-canvas-utils';
// Optional: Define a type for your parsed data if using a parser
interface MyParsedData {
summary: string;
keywords: string[];
}
// Optional: Define a custom parser function
const myCustomParser = (text: string): Result<MyParsedData, Error> => {
try {
// This is a very basic parser example.
// In a real scenario, you might look for specific structures or use more robust parsing.
if (text.length < 10) { // Simulate condition where parsing isn't possible yet or fails
// For incomplete streams, you might return [null, null] or a specific error
// if you expect more data before parsing is valid.
// Or, if it's a definitive parse error:
// return [null, new Error("Text too short to parse meaningful data.")];
}
// Let's assume the AI responds with "Summary: [text] Keywords: [kw1, kw2]"
const summaryMatch = text.match(/Summary: (.*?)( Keywords:|$)/s);
const keywordsMatch = text.match(/Keywords: (.*)/s);
const summary = summaryMatch ? summaryMatch[1].trim() : "No summary found.";
const keywords = keywordsMatch ? keywordsMatch[1].split(',').map(kw => kw.trim()) : [];
if (!summaryMatch && !keywordsMatch && text.length > 0) {
// If no specific structure is found, but there's text,
// you might decide it's not an error, but just not parseable into MyParsedData.
// Depending on strictness, you could return an error or a default/empty structure.
}
return [{ summary, keywords }, null];
} catch (e) {
return [null, e instanceof Error ? e : new Error('Unknown parsing error')];
}
};
function MyTextGeneratorComponent() {
const [sendTextPrompt] = usePoeAiTextGenerator<MyParsedData>({
// simulation: true, // Enable for development
// logger: console,
});
const [isGenerating, setIsGenerating] = useState(false);
const [generatedText, setGeneratedText] = useState('');
const [parsedData, setParsedData] = useState<MyParsedData | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const handleGenerateText = useCallback((prompt: string) => {
setIsGenerating(true);
setGeneratedText('');
setParsedData(null);
setErrorMessage(null);
const callback: TextRequestCallback<MyParsedData> = (state) => {
// console.log('Text Generator State:', state);
setGeneratedText(state.text); // Always update with the latest raw text stream
if (state.error) {
setIsGenerating(false);
setErrorMessage(state.error);
} else if (state.parsed) {
setParsedData(state.parsed);
// You might set isGenerating to false here if parsing indicates completion,
// or wait for the generating flag.
}
if (!state.generating && !state.error) {
setIsGenerating(false);
// Final state, even if parsing didn't yield data or wasn't used.
}
};
// Example: Ensure window.Poe is available or simulation is enabled.
sendTextPrompt(
`@Gemini-2.5-Pro-Exp Summarize this and list keywords: ${prompt}`,
callback,
{
parser: myCustomParser, // Provide the parser
// simulatedResponseOverride: [{ messageId: 'sim1', content: 'Summary: Test. Keywords: A, B', ...}],
}
);
}, [sendTextPrompt]);
// Basic form for input
const [inputValue, setInputValue] = useState('');
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (inputValue.trim()) {
handleGenerateText(inputValue.trim());
}
};
return (
<div>
<form onSubmit={handleFormSubmit}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter text to process..."
disabled={isGenerating}
/>
<button type="submit" disabled={isGenerating}>
{isGenerating ? 'Generating...' : 'Generate Text'}
</button>
</form>
{errorMessage && <p style={{ color: 'red' }}>Error: {errorMessage}</p>}
<h4>Live Text Stream:</h4>
<pre style={{ whiteSpace: 'pre-wrap', border: '1px solid #ccc', padding: '10px' }}>
{generatedText || "Waiting for generation..."}
</pre>
{parsedData && (
<div>
<h4>Parsed Data:</h4>
<p><strong>Summary:</strong> {parsedData.summary}</p>
<p><strong>Keywords:</strong> {parsedData.keywords.join(', ')}</p>
</div>
)}
</div>
);
}
export default MyTextGeneratorComponent;3. usePoeAiMediaGenerator - Interacting with Poe AI Media Generator Models
This hook is tailored for interactions that primarily result in media attachments (e.g., image generation bots). It defaults to non-streaming.
// MyMediaGeneratorComponent.tsx
import React, { useState, useCallback } from 'react';
import {
usePoeAiMediaGenerator,
type MediaRequestState,
type MediaRequestCallback,
type PoeMessageAttachment // Type for received attachments
} from '@rizean/poe-canvas-utils';
function MyMediaGeneratorComponent() {
const [sendMediaPrompt] = usePoeAiMediaGenerator({
// simulation: true,
// logger: console,
});
const [isGenerating, setIsGenerating] = useState(false);
const [attachments, setAttachments] = useState<PoeMessageAttachment[]>([]);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [inputFiles, setInputFiles] = useState<File[]>([]); // For sending attachments
const handleGenerateMedia = useCallback((prompt: string) => {
setIsGenerating(true);
setAttachments([]);
setErrorMessage(null);
const callback: MediaRequestCallback = (state) => {
// console.log('Media Generator State:', state);
setIsGenerating(state.generating); // Reflect generating state
if (state.error) {
setErrorMessage(state.error);
} else if (state.mediaAttachments.length > 0) {
setAttachments(state.mediaAttachments);
}
// When state.generating becomes false and no error, generation is complete.
};
sendMediaPrompt(
`@ImageCreatorBot Create an image of: ${prompt}`,
callback,
{
attachments: inputFiles, // Send user-selected files with the prompt
// openChat: true, // Optional
// simulatedResponseOverride: [{ messageId: 'sim1', attachments: [{ attachmentId: 'a1', name: 'sim.png', url: '...', mimeType: 'image/png' }], ...}]
}
);
}, [sendMediaPrompt, inputFiles]);
const [inputValue, setInputValue] = useState('');
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (inputValue.trim()) {
handleGenerateMedia(inputValue.trim());
}
};
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
setInputFiles(Array.from(event.target.files));
}
};
return (
<div>
<form onSubmit={handleFormSubmit}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter media generation prompt..."
disabled={isGenerating}
/>
<br />
<label>
Optional files to send with prompt:
<input type="file" multiple onChange={handleFileChange} disabled={isGenerating} />
</label>
<br />
<button type="submit" disabled={isGenerating}>
{isGenerating ? 'Generating Media...' : 'Generate Media'}
</button>
</form>
{errorMessage && <p style={{ color: 'red' }}>Error: {errorMessage}</p>}
{attachments.length > 0 && (
<div>
<h4>Generated Media:</h4>
<ul>
{attachments.map(att => (
<li key={att.attachmentId}>
<a href={att.url} target="_blank" rel="noopener noreferrer">{att.name}</a>
({att.mimeType})
{att.mimeType.startsWith('image/') && <img src={att.url} alt={att.name} style={{maxWidth: '200px', display: 'block'}} />}
</li>
))}
</ul>
</div>
)}
</div>
);
}
export default MyMediaGeneratorComponent;
4. useLogger - Client-Side Logging
Useful for debugging your canvas app. Logs are stored in memory.
// MyLoggerDemoComponent.tsx
import React, { useEffect } from 'react';
import { useLogger, type LogEntry } from '@rizean/poe-canvas-utils';
function MyLoggerDemoComponent() {
// Initialize logger, optionally set a log level ('trace', 'debug', 'info', 'warn', 'error')
// Defaults to 'info' if not specified or invalid.
const { logger, logs } = useLogger('debug');
useEffect(() => {
logger.info('LoggerComponent mounted.', { timestamp: new Date().toLocaleTimeString() });
logger.debug('This is a debug message with some data:', { userId: 123, action: 'load' });
logger.warn('A warning message occurred.');
// logger.error('An example error!', new Error('Something went wrong'));
logger.trace('This trace message will only show if logLevel is "trace".');
}, [logger]);
return (
<div>
<h3>Application Logs (In-Memory):</h3>
{logs.length === 0 ? (
<p>No logs yet.</p>
) : (
<ul style={{ listStyleType: 'none', padding: 0 }}>
{logs.map((log: LogEntry, index: number) => (
<li key={index} style={{ marginBottom: '5px', borderBottom: '1px solid #eee', paddingBottom: '5px' }}>
<span style={{ fontWeight: 'bold' }}>[{new Date(log.timestamp).toLocaleTimeString()}]</span>
<span style={{ marginLeft: '8px', color: log.type === 'error' ? 'red' : log.type === 'warn' ? 'orange' : 'inherit' }}>
[{log.type.toUpperCase()}]
</span>
<pre style={{ margin: '0 0 0 10px', display: 'inline-block', whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
{typeof log.message === 'string' ? log.message : JSON.stringify(log.message)}
</pre>
</li>
))}
</ul>
)}
</div>
);
}
export default MyLoggerDemoComponent;5. tryCatchSync and tryCatchAsync - Functional Error Handling
Wrap functions to handle errors without traditional try-catch blocks in your main logic.
// tryCatchExample.ts
import { tryCatchSync, tryCatchAsync, Result } from '@rizean/poe-canvas-utils';
// Synchronous example
function mightThrowSync(shouldThrow: boolean): string {
if (shouldThrow) {
throw new Error("Synchronous error!");
}
return "Success!";
}
const [dataSync, errorSync]: Result<string, Error> = tryCatchSync(() => mightThrowSync(true));
if (errorSync) {
console.error("Caught sync error:", errorSync.message);
} else {
console.log("Sync result:", dataSync);
}
// Asynchronous example
async function mightRejectAsync(shouldReject: boolean): Promise<string> {
if (shouldReject) {
return Promise.reject(new Error("Asynchronous error!"));
}
return Promise.resolve("Async Success!");
}
async function runAsyncExample() {
const [dataAsync, errorAsync]: Result<string, Error> = await tryCatchAsync(() => mightRejectAsync(true));
if (errorAsync) {
console.error("Caught async error:", errorAsync.message);
} else {
console.log("Async result:", dataAsync);
}
}
runAsyncExample();6. applyGeminiThinkingFilter - Cleaning AI Output
Removes "Thinking..." blocks from text generated by Gemini models.
// geminiFilterExample.ts
import { applyGeminiThinkingFilter } from '@rizean/poe-canvas-utils';
const rawGeminiOutput = `
Some initial text.
*Thinking...*
> This is part of the thinking process.
> More thinking steps.
This is the actual response after thinking.
`;
const cleanedOutput = applyGeminiThinkingFilter(rawGeminiOutput);
console.log("Raw Output:\n", rawGeminiOutput);
console.log("Cleaned Output:\n", cleanedOutput);
// Expected Cleaned Output:
// "This is the actual response after thinking."
// (Leading/trailing newlines might vary based on exact input)7. saveDataToFile and loadDataFromFile - Data Persistence
Allows users to download their application state as a JSON file and upload it later. This is crucial for Poe Canvas Apps which lack traditional browser storage.
// MyDataPersistenceComponent.tsx
import React, { useState, useCallback } from 'react';
import {
saveDataToFile,
loadDataFromFile,
type VersionedData,
type StorageLoadOptions
} from '@rizean/poe-canvas-utils';
// Define your application's data structure
interface AppDataV1 extends VersionedData {
version: 1;
notes: string[];
settings: { theme: string };
}
interface AppDataV2 extends VersionedData {
version: 2; // New version
userNotes: Array<{ id: string; text: string }>; // Changed structure
preferences: {
theme: string;
fontSize: number;
};
}
// Current version of your app's data
const CURRENT_APP_VERSION = 2;
const DATA_FILENAME = 'my-app-data.json';
function MyDataPersistenceComponent() {
const [appData, setAppData] = useState<AppDataV2 | null>(null);
const [statusMessage, setStatusMessage] = useState('');
const handleSaveData = useCallback(() => {
if (!appData) {
setStatusMessage('No data to save.');
return;
}
const [success, error] = saveDataToFile(DATA_FILENAME, appData);
if (success) {
setStatusMessage(`Data saved to ${DATA_FILENAME}!`);
} else {
setStatusMessage(`Error saving data: ${error?.message}`);
}
}, [appData]);
const handleLoadData = useCallback(async () => {
const loadOptions: StorageLoadOptions<AppDataV2> = {
currentVersion: CURRENT_APP_VERSION,
migrate: async (loadedUntypedData: any, loadedVersion: number): Promise<AppDataV2> => {
setStatusMessage(`Migrating data from v${loadedVersion} to v${CURRENT_APP_VERSION}...`);
if (loadedVersion === 1) {
// Example: Migrate from V1 to V2
const oldData = loadedUntypedData as AppDataV1;
return {
version: 2,
userNotes: oldData.notes.map((note, index) => ({ id: `note-${index}`, text: note })),
preferences: {
theme: oldData.settings.theme,
fontSize: 14, // New default
},
};
}
// If more versions, add more migration steps here
throw new Error(`Migration from version ${loadedVersion} not supported.`);
},
validate: async (dataToValidate: AppDataV2): Promise<boolean> => {
// Example validation: ensure preferences exist
const isValid = dataToValidate.preferences && typeof dataToValidate.preferences.theme === 'string';
if (!isValid) setStatusMessage('Validation failed: Invalid preferences structure.');
return isValid;
},
};
const [loadedData, error] = await loadDataFromFile<AppDataV2>(loadOptions);
if (error) {
setStatusMessage(`Error loading data: ${error.message}`);
setAppData(null);
} else if (loadedData) {
setAppData(loadedData);
setStatusMessage('Data loaded successfully!');
} else {
setStatusMessage('File selection cancelled or no data loaded.');
}
}, []);
// Initialize with some default data for V2
useEffect(() => {
setAppData({
version: CURRENT_APP_VERSION,
userNotes: [{id: '1', text: "Hello World"}],
preferences: { theme: 'dark', fontSize: 16}
});
}, []);
return (
<div>
<h3>Data Persistence Example</h3>
<button onClick={handleSaveData} disabled={!appData}>Save Data to File</button>
<button onClick={handleLoadData}>Load Data from File</button>
{statusMessage && <p><i>{statusMessage}</i></p>}
<h4>Current App Data:</h4>
<pre>{appData ? JSON.stringify(appData, null, 2) : 'No data loaded.'}</pre>
</div>
);
}
export default MyDataPersistenceComponent;8. complexResponseParser - Parsing Complex AI Responses
A utility function to parse structured AI responses that might contain a main textual part enclosed in configurable tags and an optional JSON data block.
// complexParserExample.ts
import { complexResponseParser, type ParsedComplexAiResponse, type ComplexParserOptions } from '@rizean/poe-canvas-utils';
const exampleAiResponseDefaultTags = `
Some initial chatter from the AI.
*Thinking...*
> Okay, planning to respond.
<response>
This is the primary textual answer.
It can span multiple lines.
</response>
Follow-up text.
\`\`\`json
{
"id": 123,
"status": "completed",
"details": {
"itemsProcessed": 5,
"warnings": ["Low accuracy on item 3"]
}
}
\`\`\`
Final remarks.
`;
const exampleAiResponseCustomTags = `
AI is starting...
[CHAT_START]
Hello! This is the chat content.
[CHAT_END]
\`\`\`json
{"user": "guest", "session": "xyz789"}
\`\`\`
`;
const exampleAiResponseNoJson = `
<response>
Just a simple text response.
</response>
`;
const exampleAiResponseMalformedJson = `
<response>
Text part is okay.
</response>
\`\`\`json
{ "data": "value", "invalid: json }
\`\`\`
`;
// 1. Using default tags (<response>, </response>)
const [parsedDefault, errorDefault] = complexResponseParser(exampleAiResponseDefaultTags);
if (errorDefault) {
console.error("Default Parser Error:", errorDefault.message);
} else if (parsedDefault) {
console.log("Parsed with Default Tags:");
console.log(" Response:", parsedDefault.response); // "This is the primary textual answer.\nIt can span multiple lines."
console.log(" Data:", parsedDefault.data);
// Data: { id: 123, status: "completed", details: { itemsProcessed: 5, warnings: ["Low accuracy on item 3"] } }
}
// 2. Using custom tags
const customOptions: ComplexParserOptions = {
responseStartTag: "[CHAT_START]",
responseEndTag: "[CHAT_END]"
};
const [parsedCustom, errorCustom] = complexResponseParser(exampleAiResponseCustomTags, customOptions);
if (errorCustom) {
console.error("Custom Parser Error:", errorCustom.message);
} else if (parsedCustom) {
console.log("\nParsed with Custom Tags:");
console.log(" Response:", parsedCustom.response); // "Hello! This is the chat content."
console.log(" Data:", parsedCustom.data); // Data: { user: "guest", session: "xyz789" }
}
// 3. Response with no JSON
const [parsedNoJson, errorNoJson] = complexResponseParser(exampleAiResponseNoJson);
if (parsedNoJson) {
console.log("\nParsed with No JSON:");
console.log(" Response:", parsedNoJson.response); // "Just a simple text response."
console.log(" Data exists:", 'data' in parsedNoJson); // false
}
// 4. Response with malformed JSON
const [parsedMalformed, errorMalformed] = complexResponseParser(exampleAiResponseMalformedJson);
if (errorMalformed) {
console.error("\nMalformed JSON Error:", errorMalformed.message); // Will show JSON parsing error
}
// This parser can be used with usePoeAiTextGenerator:
//
// import { usePoeAiTextGenerator } from '@rizean/poe-canvas-utils';
// import { complexResponseParser, type ParsedComplexAiResponse } from '@rizean/poe-canvas-utils';
//
// const [sendPrompt] = usePoeAiTextGenerator<ParsedComplexAiResponse>();
//
// sendPrompt("query", (state) => {
// if (state.parsed) {
// // state.parsed.response
// // state.parsed.data
// }
// }, { parser: complexResponseParser });
//
// // To use custom tags with the text generator:
// sendPrompt("query", (state) => { /* ... */ }, {
// parser: (text) => complexResponseParser(text, { responseStartTag: "[S]", responseEndTag: "[E]" })
// });
API Reference
usePoeAi(options?: UseAiOptions)
- Returns:
[(prompt: string, callback: RequestCallback, requestOptions?: RequestOptions) => void] sendToAIfunction to initiate requests.UseAiOptions:handler?: string: Custom handler name.simulation?: boolean: Enable/disable simulation.simulationDelay?: number: Delay for simulated responses.simulateErrorChance?: number: Chance of simulated error (0-100).simulationResponses?: Message[] | null: Default simulated messages.logger?: Logger: Custom logger instance.RequestCallback:(state: RequestState) => voidRequestState:{ requestId, generating, error, responses, status }RequestOptions:{ stream?, openChat?, simulatedResponseOverride?, attachments? }
usePoeAiTextGenerator<T = undefined>(options?: UseAiTextOptions)
- Returns:
[(prompt: string, callback: TextRequestCallback<T>, requestOptions?: TextRequestOptions<T>) => Promise<void>]- A tuple containing a single function (typically named
sendTextMessageorsendTextPrompt) to initiate text generation requests.
- A tuple containing a single function (typically named
UseAiTextOptions: ExtendsPoeUseAiOptions(the options for the baseusePoeAihook). Allows configuring simulation, logger, etc., specifically for this text generator instance.TextRequestCallback<T>:(state: TextRequestState<T>) => void- A callback function invoked with state updates during the text generation lifecycle.
Tis the type of theparseddata if a parser is used.
- A callback function invoked with state updates during the text generation lifecycle.
TextRequestState<T>:requestId: string: Unique ID for the request.generating: boolean: True if the AI is still generating text.error: string | null: An error message if an error occurred (from AI or parser).text: string: The raw (or partially streamed) text content from the AI.parsed?: T: The output from the providedparserfunction, if any.Tis the type of the parsed data.rawResponse?: PoeAiRequestState | null: The raw state object from the underlyingusePoeAihook, for debugging or advanced use.
TextRequestOptions<T>:simulatedResponseOverride?: PoeMessage[] | null: Specific simulated messages for this request, overriding global simulation settings.parser?: (text: string) => Result<T, Error>: An optional function to parse the AI'stextresponse. It should handle potentially incomplete text (during streaming) and return aResulttuple ([parsedData, null]on success, or[null, error]on failure).
usePoeAiMediaGenerator(options?: UseAiMediaOptions)
- Returns:
[(prompt: string, callback: MediaRequestCallback, requestOptions?: MediaRequestOptions) => Promise<void>]- A tuple containing a single function (typically named
sendMediaMessageorsendMediaPrompt) to initiate media generation requests.
- A tuple containing a single function (typically named
UseAiMediaOptions: ExtendsPoeUseAiOptions. Allows configuring simulation, logger, etc., for this media generator instance.MediaRequestCallback:(state: MediaRequestState) => void- A callback function invoked with state updates during the media generation lifecycle.
MediaRequestState:requestId: string: Unique ID for the request.generating: boolean: True if the AI is still generating media.error: string | null: An error message if an error occurred.mediaAttachments: PoeMessageAttachment[]: An array ofPoeMessageAttachmentobjects representing the generated media.rawResponse?: PoeAiRequestState | null: The raw state object from the underlyingusePoeAihook.
MediaRequestOptions:simulatedResponseOverride?: PoeMessage[] | null: Specific simulated messages for this request.attachments?: File[]: An array ofFileobjects to send with the prompt to the AI.openChat?: boolean: Whether to attempt to open the Poe chat interface (defaults tofalseor as perusePoeAidefault).
useLogger(logLevelInput?: string)
- Returns:
{ logs: LogEntry[], logger: Logger } logLevelInput:'trace' | 'debug' | 'info' | 'warn' | 'error'(defaults to'info').LogEntry:{ type: string, message: unknown, timestamp: Date }Logger: Interface with methods likedebug(),info(),error(), etc.
tryCatchSync<T, E = Error>(fn: () => T, mapError?: (caughtError: unknown) => E)
- Returns:
Result<T, E>which is[T, null] | [null, E]
tryCatchAsync<T, E = Error>(fn: () => Promise<T>, mapError?: (caughtError: unknown) => E)
- Returns:
Promise<Result<T, E>>
applyGeminiThinkingFilter(text: string)
- Returns:
string(filtered text)
saveDataToFile<T extends VersionedData>(filename: string, data: T)
- Returns:
Result<true, Error> - Triggers a file download.
loadDataFromFile<T extends VersionedData>(options: StorageLoadOptions<T>)
- Returns:
Promise<Result<T | null, Error | null>> - Prompts user for file upload.
StorageLoadOptions<T>:currentVersion: number: Your application's current data structure version.migrate?: (loadedData: any, loadedVersion: number) => T | Promise<T>: Function to migrate older data structures.validate?: (data: T) => boolean | Promise<boolean>: Function to validate loaded (and possibly migrated) data.VersionedData: Interface{ version: number; [key: string]: any; }that your data structure must implement.
complexResponseParser(rawText: string, options?: ComplexParserOptions)
- Returns:
Result<ParsedComplexAiResponse, Error>- A
Resulttuple:[ParsedComplexAiResponse, null]on successful parsing, or[null, Error]if an error occurs (e.g., malformed JSON within a declared JSON block).
- A
rawText: string: The raw string output from the AI to be parsed.options?: ComplexParserOptions:responseStartTag?: string: Custom string marking the beginning of the main response block (defaults to"<response>").responseEndTag?: string: Custom string marking the end of the main response block (defaults to"</response>").
ParsedComplexAiResponse(Interface for the successfully parsed data):response: string: The extracted textual content from between the response tags.data?: unknown: The parsed data from the```json ... ```block, if present and valid. This key is absent if no JSON block is found.
Poe Types
The library exports various types from src/types/Poe.ts (e.g., PoeMessage, PoeMessageAttachment, PoeSendUserMessageResult, PoeEmbedAPIError) for strong typing when working with the Poe API. It also augments the global Window interface to include window.Poe.
Contributing
Contributions are welcome! Please open an issue or submit a pull request to the GitHub repository.
