Skip to content

Commit

Permalink
Merge pull request #32 from salesforcecli/wr/agentPreview
Browse files Browse the repository at this point in the history
Wr/agent preview
  • Loading branch information
iowillhoit authored Dec 6, 2024
2 parents 8b69c38 + a6ec51d commit 3999aca
Show file tree
Hide file tree
Showing 9 changed files with 933 additions and 9 deletions.
10 changes: 9 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
module.exports = {
extends: ['eslint-config-salesforce-typescript', 'eslint-config-salesforce-license', 'plugin:sf-plugin/recommended'],
extends: [
'eslint-config-salesforce-typescript',
'eslint-config-salesforce-license',
'plugin:sf-plugin/recommended',
'xo-react/space',
],
root: true,
rules: {
'react/jsx-tag-spacing': 'off',
},
};
8 changes: 8 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
],
"plugin": "@salesforce/plugin-agent"
},
{
"alias": [],
"command": "agent:preview",
"flagAliases": [],
"flagChars": ["n", "o"],
"flags": ["api-version", "flags-dir", "name", "target-org"],
"plugin": "@salesforce/plugin-agent"
},
{
"alias": [],
"command": "agent:test:cancel",
Expand Down
20 changes: 20 additions & 0 deletions messages/agent.preview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# summary

Interact with an active agent, as a user would, to preview responses

# description

XXX

# flags.name.summary

The name of the agent you want to preview

# flags.name.description

the API name of the agent? (TBD based on agents library)

# examples

- <%= config.bin %> <%= command.id %> --agent HelpDeskAgent
- <%= config.bin %> <%= command.id %> --agent ConciergeAgent --target-org production
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@
"@salesforce/core": "^8.8.0",
"@salesforce/kit": "^3.2.1",
"@salesforce/sf-plugins-core": "^12.1.0",
"ansis": "^3.3.2"
"ansis": "^3.3.2",
"ink-text-input": "^6.0.0",
"ink": "^5.0.1",
"react": "^18.3.1"
},
"devDependencies": {
"@oclif/plugin-command-snapshot": "^5.2.19",
"@oclif/test": "^4.1.0",
"@salesforce/cli-plugins-testkit": "^5.3.35",
"@salesforce/dev-scripts": "^10.2.10",
"@salesforce/plugin-command-reference": "^3.1.29",
"@types/react": "^18.3.3",
"eslint-config-xo-react": "^0.27.0",
"eslint-config-xo": "^0.45.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-sf-plugin": "^1.20.9",
"oclif": "^4.15.12",
"ts-node": "^10.9.2",
Expand Down
9 changes: 9 additions & 0 deletions schemas/agent-preview.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/AgentPreviewResult",
"definitions": {
"AgentPreviewResult": {
"type": "null"
}
}
}
44 changes: 44 additions & 0 deletions src/commands/agent/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import React from 'react';
import { render } from 'ink';
import { AgentPreviewReact } from '../../components/agent-preview-react.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.preview');

export type AgentPreviewResult = void;

export default class AgentPreview extends SfCommand<AgentPreviewResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static readonly enableJsonFlag = false;
public static readonly requiresProject = true;

public static readonly flags = {
'target-org': Flags.requiredOrg(),
'api-version': Flags.orgApiVersion(),
name: Flags.string({
summary: messages.getMessage('flags.name.summary'),
description: messages.getMessage('flags.name.description'),
char: 'n',
required: true,
}),
};

public async run(): Promise<AgentPreviewResult> {
const { flags } = await this.parse(AgentPreview);
this.log(`previewing ${flags.name}`);

const instance = render(React.createElement(AgentPreviewReact, null));
await instance.waitUntilExit();
}
}
86 changes: 86 additions & 0 deletions src/components/agent-preview-react.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import React from 'react';
import { Box, Text } from 'ink';
import figures from '@inquirer/figures';
import TextInput from 'ink-text-input';
import { sleep } from '@salesforce/kit';

/**
* Ideas:
* - Limit height based on terminal height
* - Add keystroke to clear chat
* - Add keystroke to scroll up
* - Add keystroke to scroll down
*/
export function AgentPreviewReact(): React.ReactNode {
const [comments, setComments] = React.useState<Array<{ timestamp: Date; role: 'system' | 'user'; content: string }>>(
[]
);
const [query, setQuery] = React.useState('');
return (
<Box flexDirection="column">
{comments.length > 0 && (
<Box flexDirection="column">
{comments.map(({ timestamp, role, content }, idx) => (
<Box
key={role + '__' + timestamp.toISOString() + '__' + idx.toString()}
alignItems={role === 'user' ? 'flex-end' : 'flex-start'}
flexDirection="column"
>
<Box flexDirection="row" columnGap={1}>
<Text>{role === 'user' ? 'You' : role}</Text>
<Text color="gray">{timestamp.toLocaleString()}</Text>
</Box>
<Box
width={Math.min(process.stdout.columns - 4, content.length + 4)}
borderStyle="round"
paddingLeft={1}
paddingRight={1}
>
<Text>{content}</Text>
</Box>
</Box>
))}
<Box paddingLeft={1} paddingRight={1}>
<Text dimColor>{'─'.repeat(process.stdout.columns - 2)}</Text>
</Box>
</Box>
)}

<Box>
<Text>{figures.pointer} </Text>
<TextInput
showCursor
value={query}
placeholder="Start typing…"
onChange={setQuery}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onSubmit={async (content) => {
if (!content) return;
setQuery('');

setComments((prev) => [...prev, { role: 'user', content, timestamp: new Date() }]);
await sleep(1000);

setComments((prev) => {
const lastComment = prev[prev.length - 1];
// TODO - use agents library for generations
return (Math.random() * 2) % 2 > 1
? [
...prev,
{ role: 'system', content: "I'm sorry, I can't help with this request", timestamp: new Date() },
]
: [...prev, { ...lastComment, role: 'system', content: 'We have a scuba class at 9:30 AM' }];
});
}}
/>
</Box>
</Box>
);
}
5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "@salesforce/dev-config/tsconfig-strict-esm",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
"rootDir": "src",
"jsx": "react"
},
"include": ["./src/**/*.ts"]
"include": ["./src/**/*.ts", "./src/**/*.tsx"]
}
Loading

0 comments on commit 3999aca

Please sign in to comment.