-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #649 from near/develop
weekly promotion of develop to main
- Loading branch information
Showing
9 changed files
with
1,293 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); |
Oops, something went wrong.