Skip to content

Commit

Permalink
Merge pull request #649 from near/develop
Browse files Browse the repository at this point in the history
weekly promotion of develop to main
  • Loading branch information
calebjacob authored Feb 15, 2024
2 parents 58e4d64 + e96779f commit e4441a8
Show file tree
Hide file tree
Showing 9 changed files with 1,293 additions and 7 deletions.
122 changes: 122 additions & 0 deletions indexers/agents/agents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { Block } from "@near-lake/primitives";
/**
* Note: We only support javascript at the moment. We will support Rust, Typescript in a further release.
*/

/**
* getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database.
* context is a global variable that contains helper methods.
* context.db is a subfield which contains helper methods to interact with your database.
*
* Learn more about indexers here: https://docs.near.org/concepts/advanced/indexers
*
* @param {block} Block - A Near Protocol Block
*/
async function getBlock(block: Block) {
function base64decode(encodedValue) {
try {
const buff = Buffer.from(encodedValue, "base64");
const str = buff.toString("utf-8").replace(/\\xa0/g, " ");
return JSON.parse(str);
} catch (error) {
console.log(
'Error parsing JSON - skipping data for "functionCallOperation.args"',
error
);
}
}

const getFunctionCallsFunction = (block, contract, method) => {
return block
.actions()
.filter((action) => action.receiverId === contract)
.flatMap((action) =>
action.operations
.map((operation) => operation["FunctionCall"])
.filter((operation) => operation?.methodName === method)
.map((functionCallOperation) => ({
...functionCallOperation,
args: base64decode(functionCallOperation.args),
receiptId: action.receiptId,
}))
.filter((a) =>
block
.receipts()
.find((r) => r.receiptId === a.receiptId)
.status.hasOwnProperty("SuccessValue")
)
);
};

const getSocialOperations = (block, operation) => {
const contract = "social.near";
const method = "set";
return getFunctionCallsFunction(block, contract, method)
.filter((functionCall) => {
if (
!functionCall ||
!functionCall.args ||
!functionCall.args.data ||
!Object.keys(functionCall.args.data) ||
!Object.keys(functionCall.args.data)[0]
) {
console.log("Set operation did not have arg data in expected format");
return;
}
const accountId = Object.keys(functionCall.args.data)[0];
const accountData = functionCall.args.data[accountId];
if (!accountData) {
console.log(
"Set operation did not have arg data for accountId in expected format"
);
return;
}
return accountData[operation];
})
.map((functionCall) => {
const accountId = Object.keys(functionCall.args.data)[0];
return {
accountId,
data: functionCall.args.data[accountId][operation],
};
});
};

const agentWrites = getSocialOperations(block, "agent");
agentWrites.map(async ({ accountId, data }) => {
try {
const agent = {
name: data.name,
account_id: accountId,
display_name: data.displayName,
preferred_provider: data.preferredProvider,
preferred_model: data.preferredModel,
prompt: data.prompt,
variables: data.variables,
component: data.component,
logo_url: data.logoUrl,
tools: data.tools,
properties: data.properties,
};
await context.db.Agents.upsert(
agent,
["account_id", "name"],
[
"display_name",
"preferred_provider",
"preferred_model",
"prompt",
"variables",
"component",
"logo_url",
"tools",
"properties",
]
);

console.log(`Agent from ${accountId} has been added to the database`);
} catch (e) {
console.log(`Failed to store agent from ${accountId} to the database`, e);
}
});
}
20 changes: 20 additions & 0 deletions indexers/agents/agents.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CREATE TABLE
"agents" (
"id" SERIAL NOT NULL,
"account_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"display_name" TEXT,
"preferred_provider" TEXT,
"preferred_model" TEXT,
"input_adaptor" TEXT,
"output_adaptor" TEXT,
"prompt" TEXT,
"variables" TEXT,
"component" TEXT,
"logo_url" TEXT,
"tools" JSONB,
"properties" JSONB,
PRIMARY KEY ("id")
);

ALTER TABLE "agents" ADD CONSTRAINT "unique_name" UNIQUE ("account_id", "name");
98 changes: 98 additions & 0 deletions src/Entities/AgentFramework/AgentCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const { href } = VM.require("devhub.near/widget/core.lib.url");

if (!href) {
return <></>;
}

const Card = styled.div`
cursor: pointer;
background-color: white;
border-radius: 0.5rem;
padding: 1.5rem;
gap: 1rem;
height: 100%;
min-height: 12rem;
transition: all 300ms;
box-shadow:
0 1px 3px 0 rgb(0 0 0 / 0.1),
0 1px 2px -1px rgb(0 0 0 / 0.1);
&:hover {
box-shadow:
0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
}
img.logo {
height: 6rem;
width: 6rem;
border-radius: 50%;
object-fit: cover;
}
h3,
p {
margin: 0;
}
h3 {
font-size: 1.25rem;
font-weight: 600;
}
p {
font-size: 1rem;
font-weight: 400;
}
`;
const CardBody = styled.div`
display: flex;
align-items: center;
`;
const Break = styled.div`
flex-basis: 100%;
height: 0;
`;
const Actions = styled.div`
padding-top: 16px;
display: flex;
align-items: center;
gap: 12px;
`;
const AgentCard = ({ item }) => {
const { accountId, name, displayName, prompt, logoUrl } = item;
const imageUrl =
logoUrl ?? "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu";
const link = href({
widgetSrc: `${REPL_ACCOUNT}/widget/Entities.AgentFramework.AgentChat`,
params: { src: `${accountId}/agent/${name}` },
});

return (
<Card>
<Link to={link} style={{ all: "unset" }}>
<div className="row">
<div className="col-4">
<img className="logo" src={imageUrl} alt="agent logo" />
</div>

<div className="col">
<h3>{displayName}</h3>
<p>by {accountId}</p>
<Widget
src="near/widget/DIG.Tooltip"
props={{
content: <span style={{ whiteSpace: "pre-line" }}>{prompt}</span>,
trigger: <p>{prompt ? prompt.substring(0, 20) : ""}...</p>,
}}
/>
</div>
</div>
</Link>
</Card>
);
};

return AgentCard(props);
107 changes: 107 additions & 0 deletions src/Entities/AgentFramework/AgentChat.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const [accountId, agentType, agentName] = props.src.split("/") ?? [null, null, null];
const blockHeight = props.blockHeight ?? "final";

const data = Social.getr(`${accountId}/agent/${agentName}`, blockHeight);

if (!data) return "Loading...";

const [question, setQuestion] = useState("");
const [prompt, setPrompt] = useState("");
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState("");
const [messages, setMessages] = useState([]);

useEffect(() => {
if (messages.length === 0 || messages[messages.length - 1].role !== "user") {
return;
}
console.log(messages);
setLoading(true);
asyncFetch(`https://ai.near.social/api`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
responseType: "json",
body: JSON.stringify([{ role: "system", content: data.prompt }, ...messages.slice(-1)]),
})
.then(({ body }) => {
setMessages([...messages, { role: "system", content: body.response }]);
})
.finally(() => {
setLoading(false);
});
}, [messages]);

const submitQuestion = () => {
setMessages([...messages, { role: "user", content: question }]);
setQuestion("");
};

const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: 48px;
`;

return (
<Wrapper>
<div>
<div>Name: {data.displayName}</div>
<div>
Prompt: <pre>{data.prompt}</pre>
</div>

<div className="mb-3">
<div className="input-group">
<input
type="text"
className="form-control"
style={{
borderTopLeftRadius: "2rem",
borderBottomLeftRadius: "2rem",
}}
value={question}
onChange={(e) => setQuestion(e.target.value)}
onKeyPress={(e) => {
if (e.key === "Enter") {
submitQuestion();
}
}}
placeholder="What's your question?"
autoFocus
/>
<button
className="btn btn-primary"
style={{
borderTopRightRadius: "2rem",
borderBottomRightRadius: "2rem",
}}
onClick={() => submitQuestion()}
>
Submit
</button>
</div>
</div>
<div className="d-flex flex-column-reverse">
{messages.map(({ role, content }, i) => {
return (
<div key={i} className={`message ${role}`}>
{role === "user" && (
<Widget src="mob.near/widget/N.ProfileLine" props={{ accountId: context.accountId }} />
)}
<Markdown text={content} />
</div>
);
})}
{loading && (
<div key="loading" className={`message system`}>
<div>
<span className="spinner-grow spinner-grow-sm me-1" role="status" aria-hidden="true" />
</div>
</div>
)}
</div>
</div>
</Wrapper>
);
Loading

0 comments on commit e4441a8

Please sign in to comment.