Skip to content

Commit

Permalink
AI proxy test: start and refund conversation (#26)
Browse files Browse the repository at this point in the history
* start test conversation from the browser

* test refund message

* test refund transaction

* remove near-api-js from package.json

* fix nft mint is forbidden test after upgrade of near-api-js

* adjustments for video

* update readme to reflect hashed conversation id
  • Loading branch information
petersalomonsen authored Dec 25, 2024
1 parent 0939aff commit d34e892
Show file tree
Hide file tree
Showing 12 changed files with 857 additions and 656 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ emsdk
/blob-report/
/playwright/.cache/
bin

*.mp4
2 changes: 1 addition & 1 deletion examples/aiproxy/.test.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SPIN_VARIABLE_OPENAI_API_KEY=abcd
SPIN_VARIABLE_REFUND_SIGNING_KEY=defgh
SPIN_VARIABLE_REFUND_SIGNING_KEY=48QM3KLHFY22hDNnDx6zvgakY1dy66Jsv4dtTT6mt131DtjvPrQn7zyr3CVb1ZKPuVLbmvjQSK9o5vuEvMyiLR5Y
SPIN_VARIABLE_FT_CONTRACT_ID=aitoken.test.near
SPIN_VARIABLE_OPENAI_COMPLETIONS_ENDPOINT=http://127.0.0.1:3001/v1/chat/completions
SPIN_VARIABLE_RPC_URL=http://localhost:14500
23 changes: 19 additions & 4 deletions examples/aiproxy/playwright-tests/aiproxy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@
import { test, expect } from '@playwright/test';

test('ask question', async ({ page }) => {
const { functionAccessKeyPair, publicKey, accountId, contractId } = await fetch('http://localhost:14501').then(r => r.json());

await page.goto('/');
await page.evaluate(({ accountId, publicKey, functionAccessKeyPair, contractId }) => {
localStorage.setItem("aiproxy_wallet_auth_key", JSON.stringify({ accountId, allKeys: [publicKey] }));
localStorage.setItem(`near-api-js:keystore:${accountId}:sandbox`, functionAccessKeyPair);
localStorage.setItem(`contractId`, contractId);
}, { accountId, publicKey, functionAccessKeyPair, contractId });

await page.reload();

const {real_conversation_id} = await fetch('http://localhost:14501').then(r => r.json());
await page.getByPlaceholder('conversation id').fill(real_conversation_id);
await page.getByRole('button', { name: 'Start conversation' }).click();

const questionArea = await page.getByPlaceholder('Type your question here...');
await expect(questionArea).toBeEnabled();
questionArea.fill("Hello!");
await page.getByRole('button', { name: 'Ask OpenAI' }).click();
await expect(await page.getByText("I am just a mockserver")).toBeVisible();
await page.waitForTimeout(500);
await page.getByRole('button', { name: 'Ask AI' }).click();
await expect(await page.getByText("Hello! How can I assist you today?")).toBeVisible();

await page.waitForTimeout(500);
await page.locator("#refundButton").click();

await expect(await page.locator("#refund_message")).toContainText(`EVENT_JSON:{"standard":"nep141","version":"1.0.0","event":"ft_transfer","data":[{"old_owner_id":"${contractId}","new_owner_id":"${accountId}","amount":"127999973"}]}\nrefunded 127999973 to ${accountId}`);
});
45 changes: 31 additions & 14 deletions examples/aiproxy/playwright-tests/near_rpc.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,66 @@
import { Worker } from 'near-workspaces';
import { KeyPairEd25519, KeyPair, parseNEAR, Worker } from 'near-workspaces';

import { readFile } from 'fs/promises';
import { createServer } from "http";
import { createHash } from 'crypto';


const worker = await Worker.init({ port: 14500, rpcAddr: "http://localhost:14500" });

process.on('exit', () => {
console.log('Tearing down sandbox worker');
worker.tearDown();
});

const aiTokenAccount = await worker.rootAccount.createAccount('aitoken.test.near');
await aiTokenAccount.deploy(await readFile('../fungibletoken/out/fungible_token.wasm'));
await aiTokenAccount.call(aiTokenAccount.accountId, 'new_default_meta', { owner_id: aiTokenAccount.accountId, total_supply: 1_000_000_000_000n.toString() });

const publicKeyBytes = KeyPair.fromString('ed25519:'+process.env.SPIN_VARIABLE_REFUND_SIGNING_KEY).getPublicKey().data;

const javascript = (await readFile(new URL('../../fungibletoken/e2e/aiconversation.js', import.meta.url))).toString()
.replace("REPLACE_REFUND_SIGNATURE_PUBLIC_KEY", JSON.stringify(Array.from((await aiTokenAccount.getKey()).getPublicKey().data)));
.replace("REPLACE_REFUND_SIGNATURE_PUBLIC_KEY", JSON.stringify(Array.from(publicKeyBytes)));

await aiTokenAccount.call(
aiTokenAccount.accountId,
'post_javascript',
{javascript}
'post_javascript',
{ javascript }
);

const alice = await worker.rootAccount.createAccount('alice.test.near');
await alice.call(aiTokenAccount.accountId, 'storage_deposit', {
account_id: alice.accountId,
const aiuser = await worker.rootAccount.createAccount('aiuser.test.near');
await aiuser.call(aiTokenAccount.accountId, 'storage_deposit', {
account_id: aiuser.accountId,
registration_only: true,
}, {
attachedDeposit: 1_0000_0000000000_0000000000n.toString()
});

await aiTokenAccount.call(aiTokenAccount.accountId, 'ft_transfer', {
receiver_id: alice.accountId,
receiver_id: aiuser.accountId,
amount: 128_000_000n.toString(),
}, {
attachedDeposit: 1n.toString()
});

const real_conversation_id = `alice.test.near_${new Date().getTime()}`;;
const conversation_id = createHash('sha256').update(Buffer.from(real_conversation_id, 'utf8')).digest('hex');
const functionAccessKeyPair = KeyPairEd25519.fromRandom();

await alice.call(aiTokenAccount.accountId, 'call_js_func', {
function_name: "start_ai_conversation",
conversation_id
await aiuser.updateAccessKey(functionAccessKeyPair, {
permission: {
FunctionCall: {
method_names: ["call_js_func"], receiver_id: aiTokenAccount.accountId, allowance: parseNEAR("0.25").toString(),
}
}, nonce: 0
});

const publicKey = (await aiuser.getKey()).getPublicKey().toString();

const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
real_conversation_id
publicKey,
functionAccessKeyPair: functionAccessKeyPair.toString(),
accountId: aiuser.accountId,
contractId: aiTokenAccount.accountId
}));
});

Expand Down
12 changes: 11 additions & 1 deletion examples/aiproxy/playwright-tests/openaimockserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ const server = createServer((req, res) => {
choices: [
{
delta: {
content: "I am just a mockserver\n",
content: "Hello! How can I assist you today?\n",
},
},
],
usage: null
}),
JSON.stringify({
choices: [
Expand All @@ -30,6 +31,15 @@ const server = createServer((req, res) => {
},
},
],
usage:
{
"prompt_tokens": 18,
"completion_tokens": 9,
"total_tokens": 27,
"prompt_tokens_details":
{ "cached_tokens": 0 },
"completion_tokens_details": { "reasoning_tokens": 0 }
}
}),
"[DONE]",
];
Expand Down
3 changes: 2 additions & 1 deletion examples/aiproxy/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default defineConfig({
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://127.0.0.1:8080',
video: 'off',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
Expand Down Expand Up @@ -79,7 +80,7 @@ export default defineConfig({
reuseExistingServer: !process.env.CI,
},
{
command: "node playwright-tests/near_rpc.js",
command: "export $(grep -v '^#' .test.env | xargs) && node playwright-tests/near_rpc.js",
url: 'http://127.0.0.1:14501',
reuseExistingServer: !process.env.CI,
}],
Expand Down
Loading

0 comments on commit d34e892

Please sign in to comment.