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-utils
Using npm:
npm install @rizean/poe-canvas-utils
Using yarn:
yarn add @rizean/poe-canvas-utils
Usage 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.