feat(chat): use Streamdown for markdown rendering in chat messages
- Add streamdown package for AI-optimized markdown streaming - Update dashboard and public agent chat pages to render messages with Streamdown - Enables proper markdown formatting (code blocks, lists, etc.) in chat responses
This commit is contained in:
parent
27e7d45646
commit
1c903cd26e
5 changed files with 252 additions and 5 deletions
|
|
@ -57,6 +57,7 @@
|
|||
"remark-gfm": "^4.0.1",
|
||||
"resend": "^6.7.0",
|
||||
"sonner": "^2.0.7",
|
||||
"streamdown": "^1.6.11",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import Link from 'next/link';
|
|||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
|
||||
import { Streamdown } from 'streamdown';
|
||||
import { AppHeader } from '~/components/AppHeader';
|
||||
import { LikeButton } from '~/components/LikeButton';
|
||||
|
||||
|
|
@ -807,7 +808,9 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
|||
{streamingContent && (
|
||||
<div className="flex justify-start">
|
||||
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
|
||||
<p className="whitespace-pre-wrap text-sm">{streamingContent}</p>
|
||||
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
|
||||
<Streamdown>{streamingContent}</Streamdown>
|
||||
</div>
|
||||
<span className="inline-block w-2 h-4 bg-primary animate-pulse ml-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -884,7 +887,9 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
|||
{message.role === 'USER' && (
|
||||
<div className="flex justify-end">
|
||||
<div className="max-w-[80%] rounded-lg p-4 bg-primary text-primary-foreground">
|
||||
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
|
||||
<div className="text-sm prose prose-sm prose-invert max-w-none">
|
||||
<Streamdown>{message.content}</Streamdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -896,7 +901,9 @@ export default function PublicAgentChatPage(): React.ReactElement {
|
|||
{message.content && (
|
||||
<div className="flex justify-start">
|
||||
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
|
||||
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
|
||||
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
|
||||
<Streamdown>{message.content}</Streamdown>
|
||||
</div>
|
||||
{/* Token usage for debugging */}
|
||||
{(message.inputTokens || message.outputTokens) && (
|
||||
<div className="mt-2 pt-2 border-t border-border/50 text-[10px] text-foreground-tertiary font-mono">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Icon } from '@tpmjs/ui/Icon/Icon';
|
|||
import Link from 'next/link';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Streamdown } from 'streamdown';
|
||||
import { DashboardLayout } from '~/components/dashboard/DashboardLayout';
|
||||
|
||||
interface Agent {
|
||||
|
|
@ -678,7 +679,9 @@ export default function AgentChatPage(): React.ReactElement {
|
|||
: 'bg-surface-secondary'
|
||||
}`}
|
||||
>
|
||||
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
|
||||
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
|
||||
<Streamdown>{message.content}</Streamdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -707,7 +710,9 @@ export default function AgentChatPage(): React.ReactElement {
|
|||
{streamingContent && (
|
||||
<div className="flex justify-start">
|
||||
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
|
||||
<p className="whitespace-pre-wrap text-sm">{streamingContent}</p>
|
||||
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
|
||||
<Streamdown>{streamingContent}</Streamdown>
|
||||
</div>
|
||||
<span className="inline-block w-2 h-4 bg-primary animate-pulse ml-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -338,6 +338,9 @@ importers:
|
|||
sonner:
|
||||
specifier: ^2.0.7
|
||||
version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
streamdown:
|
||||
specifier: ^1.6.11
|
||||
version: 1.6.11(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3)
|
||||
zod:
|
||||
specifier: ^4.3.5
|
||||
version: 4.3.5
|
||||
|
|
|
|||
231
sprites_skill.md
Normal file
231
sprites_skill.md
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# Sprites Tools Development & Testing Guide
|
||||
|
||||
This document covers how to build, publish, flush caches, and test the TPMJS sprites tools.
|
||||
|
||||
## Credentials
|
||||
|
||||
### TPMJS API Key
|
||||
```
|
||||
<your-tpmjs-api-key>
|
||||
```
|
||||
|
||||
### SPRITES_TOKEN (set in collection env vars)
|
||||
```
|
||||
<your-sprites-token>
|
||||
```
|
||||
|
||||
### CRON_SECRET (for triggering syncs)
|
||||
```
|
||||
<your-cron-secret>
|
||||
```
|
||||
|
||||
## MCP Endpoint
|
||||
|
||||
The sprites collection MCP endpoint:
|
||||
```
|
||||
https://tpmjs.com/api/mcp/ajax/sprites/http
|
||||
```
|
||||
|
||||
## Available Sprites Tools
|
||||
|
||||
| Tool | Package | Description |
|
||||
|------|---------|-------------|
|
||||
| `sprites-list` | `@tpmjs/tools-sprites-list` | List all sprites with status |
|
||||
| `sprites-get` | `@tpmjs/tools-sprites-get` | Get a specific sprite's details |
|
||||
| `sprites-create` | `@tpmjs/tools-sprites-create` | Create a new sprite |
|
||||
| `sprites-delete` | `@tpmjs/tools-sprites-delete` | Delete a sprite |
|
||||
| `sprites-exec` | `@tpmjs/tools-sprites-exec` | Execute commands in a sprite |
|
||||
| `sprites-sessions` | `@tpmjs/tools-sprites-sessions` | List active sessions |
|
||||
| `sprites-policy-get` | `@tpmjs/tools-sprites-policy-get` | Get sprite policy |
|
||||
| `sprites-checkpoint-list` | `@tpmjs/tools-sprites-checkpoint-list` | List checkpoints |
|
||||
| `sprites-checkpoint-create` | `@tpmjs/tools-sprites-checkpoint-create` | Create a checkpoint |
|
||||
| `sprites-checkpoint-restore` | `@tpmjs/tools-sprites-checkpoint-restore` | Restore from checkpoint |
|
||||
|
||||
## Building a Sprites Tool
|
||||
|
||||
1. Navigate to the tool directory:
|
||||
```bash
|
||||
cd packages/tools/official/sprites-exec
|
||||
```
|
||||
|
||||
2. Build:
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
3. Type-check:
|
||||
```bash
|
||||
pnpm type-check
|
||||
```
|
||||
|
||||
## Publishing a Sprites Tool
|
||||
|
||||
1. Bump version in `package.json`:
|
||||
```json
|
||||
"version": "0.1.5"
|
||||
```
|
||||
|
||||
2. Build and publish:
|
||||
```bash
|
||||
cd packages/tools/official/sprites-exec
|
||||
pnpm build
|
||||
npm publish
|
||||
```
|
||||
|
||||
3. Commit the version bump:
|
||||
```bash
|
||||
git add package.json
|
||||
git commit -m "chore: bump sprites-exec to 0.1.5"
|
||||
git push
|
||||
```
|
||||
|
||||
## Syncing to Database
|
||||
|
||||
After publishing, sync the package to the TPMJS database:
|
||||
|
||||
### Changes Feed Sync (fast, picks up recent npm changes)
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/sync/changes" \
|
||||
-H "Authorization: Bearer <your-cron-secret>" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
### Keyword Sync (comprehensive, searches for all tpmjs packages)
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/sync/keyword" \
|
||||
-H "Authorization: Bearer <your-cron-secret>" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
### Check version in database
|
||||
```bash
|
||||
curl -s "https://tpmjs.com/api/tools?q=sprites-exec" | jq '.data[] | select(.package.npmPackageName == "@tpmjs/tools-sprites-exec") | {name: .name, version: .package.npmVersion}'
|
||||
```
|
||||
|
||||
## Flushing Executor Caches
|
||||
|
||||
**CRITICAL**: The executor caches npm packages. After publishing a new version, you MUST flush the cache.
|
||||
|
||||
### Flush Primary Executor (executor.tpmjs.com)
|
||||
```bash
|
||||
curl -s -X POST "https://executor.tpmjs.com/cache/clear" | jq .
|
||||
```
|
||||
|
||||
### Flush Railway Executor (backup)
|
||||
```bash
|
||||
curl -s -X POST "https://endearing-commitment-production.up.railway.app/cache/clear" | jq .
|
||||
```
|
||||
|
||||
### Check Cache Stats
|
||||
```bash
|
||||
curl -s "https://executor.tpmjs.com/cache/stats" | jq .
|
||||
```
|
||||
|
||||
## Testing via MCP Endpoint
|
||||
|
||||
### List All Tools
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq '.result.tools[] | {name: .name}'
|
||||
```
|
||||
|
||||
### Test sprites-list
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-list--spritesListTool","arguments":{}}}' | jq '.result'
|
||||
```
|
||||
|
||||
### Test sprites-exec (simple command)
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-exec--spritesExecTool","arguments":{"name":"hello-script-sprite","cmd":"echo hello"}}}' | jq '.result'
|
||||
```
|
||||
|
||||
### Test sprites-exec (shell operators)
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-exec--spritesExecTool","arguments":{"name":"hello-script-sprite","cmd":"echo foo && echo bar"}}}' | jq '.result'
|
||||
```
|
||||
|
||||
### Test sprites-get
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-get--spritesGetTool","arguments":{"name":"hello-script-sprite"}}}' | jq '.result'
|
||||
```
|
||||
|
||||
### Test sprites-create
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-create--spritesCreateTool","arguments":{"name":"test-sprite-123"}}}' | jq '.result'
|
||||
```
|
||||
|
||||
### Test sprites-delete
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-delete--spritesDeleteTool","arguments":{"name":"test-sprite-123"}}}' | jq '.result'
|
||||
```
|
||||
|
||||
## Testing via Agent Conversation
|
||||
|
||||
```bash
|
||||
curl -s -X POST "https://tpmjs.com/api/ajax/agents/sprites/conversation/test-conv?format=json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"message":"List all sprites"}'
|
||||
```
|
||||
|
||||
## Complete Workflow: Publish + Sync + Flush + Test
|
||||
|
||||
```bash
|
||||
# 1. Build and publish
|
||||
cd packages/tools/official/sprites-exec
|
||||
pnpm build && npm publish
|
||||
|
||||
# 2. Trigger sync
|
||||
curl -s -X POST "https://tpmjs.com/api/sync/changes" \
|
||||
-H "Authorization: Bearer <your-cron-secret>"
|
||||
|
||||
# 3. Flush caches
|
||||
curl -s -X POST "https://executor.tpmjs.com/cache/clear"
|
||||
curl -s -X POST "https://endearing-commitment-production.up.railway.app/cache/clear"
|
||||
|
||||
# 4. Test
|
||||
curl -s -X POST "https://tpmjs.com/api/mcp/ajax/sprites/http" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <your-tpmjs-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"tpmjs-tools-sprites-exec--spritesExecTool","arguments":{"name":"hello-script-sprite","cmd":"echo test"}}}' | jq .
|
||||
```
|
||||
|
||||
## Known Issues & Fixes
|
||||
|
||||
### Issue: "Failed to parse response from Sprites API"
|
||||
**Cause**: Sprites API returns JSON error `{"error":"..."}` with HTTP 200 instead of proper error codes.
|
||||
**Fix**: Added JSON error detection in sprites-exec v0.1.4.
|
||||
|
||||
### Issue: "Invalid sprite status: cold"
|
||||
**Cause**: Sprites API returns "cold" status for idle sprites, which wasn't recognized.
|
||||
**Fix**: Added "cold" to valid statuses in sprites-list v0.1.3.
|
||||
|
||||
### Issue: "The update command takes no arguments"
|
||||
**Cause**: Commands with shell operators (&&, ||, |, etc.) were being parsed incorrectly.
|
||||
**Fix**: Added shell operator detection and `sh -c` wrapping in sprites-exec v0.1.5.
|
||||
|
||||
### Issue: Old version still being used after publish
|
||||
**Cause**: Deno HTTP import cache + executor module cache.
|
||||
**Fix**:
|
||||
1. Pass explicit version from database (deployed to web app)
|
||||
2. Flush executor caches after publishing
|
||||
Loading…
Add table
Add a link
Reference in a new issue