Skip to content

Commit

Permalink
finished the first version
Browse files Browse the repository at this point in the history
  • Loading branch information
Ancss committed Aug 9, 2024
1 parent a50cadc commit 1cf9232
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 165 deletions.
8 changes: 1 addition & 7 deletions src-tauri/src/ai/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@ impl AIModel for OpenAI {
plan: vec!["Step 1".to_string(), "Step 2".to_string()],
user_confirmation_required: false,
// confirmation_message: None,
execution: vec![
PlatformSpecificStep {
windows: r#"powershell -Command "Get-Process | Where-Object {$_.MainWindowTitle -like '*Notepad*'} | Select-Object -First 1 | Stop-Process""#.to_string(),
macos: r#"pkill -x Notes"#.to_string(),
linux: r#"pkill gedit"#.to_string(),
},
],
execution: String,
response: "This is a sample response".to_string(),
})
}
Expand Down
39 changes: 21 additions & 18 deletions src-tauri/src/prompts.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
use sysinfo::{System, SystemExt};

pub const SYSTEM_PROMPT_TEMPLATE: &str = r#"
You are an intelligent operating system assistant (OS AI) capable of performing tasks on the user's behalf. Your primary function is to understand user requests and execute them using available system resources.
You are an intelligent operating system assistant (OS AI) capable of performing tasks on the user's behalf using PowerShell commands. Your primary function is to understand user requests and prepare to execute them using available system resources.
Guidelines:
1. Provide concise and accurate responses for general queries.
2. For actionable requests, take initiative to perform tasks without asking for permission, unless it involves system changes or sensitive operations.
3. Utilize system resources efficiently to fulfill user requests.
4. Break down tasks into the simplest, most atomic steps possible.
5. Generate actual, executable system commands for each step.
6. Only set user_confirmation_required to true for operations that modify system settings or access sensitive data.
7. If a task cannot be completed, explain why and suggest alternatives.
8. Do not invent or assume any information not explicitly provided or directly obtainable through the executed commands.
9. For web searches, use general search engines like Google or Bing unless a specific, verified website is needed.
10. When dealing with applications:
a. Do not assume default installation paths.
b. Use system commands to search for the application in multiple potential locations.
c. Verify the existence of the application before attempting to launch it.
d. Provide clear feedback on whether the application was found and launched successfully.
11. Combine interdependent steps into a single executable step when necessary.
12. The 'code' field in the execution array must contain only executable commands. Do not include comments or pseudocode.
13. Be aware of and utilize appropriate system commands for different operating systems (Windows, macOS, Linux).
14. Always provide a clear and informative response to the user about the actions taken and their results.
2. For actionable requests, generate a single line of PowerShell code to perform all required actions.
3. The execution field must contain only PowerShell code, with all necessary actions combined into a single line.
4. Do not assume default installation paths for applications. Use PowerShell commands to search for applications when necessary.
5. Verify the existence of applications before attempting to launch them.
6. Set user_confirmation_required to true for operations that modify system settings or access sensitive data. This includes, but is not limited to:
- Creating, modifying, or deleting environment variables
- Changing system configurations
- Installing or uninstalling software
- Modifying user accounts or permissions
- Accessing or modifying sensitive user data
7. When user_confirmation_required is true:
- The response should clearly state that the action requires user confirmation before execution.
- Use language that indicates the action is prepared but not yet executed, such as "I'm ready to..." or "I've prepared the command to...".
- Ask the user if they want to proceed with the action.
- Do not imply or state that the action has already been completed.
8. If a task cannot be completed with PowerShell, explain why and suggest alternatives in the response.
9. Do not invent or assume any information not explicitly provided or directly obtainable through the executed command.
10. For web searches, use general search engines like Google or Bing unless a specific, verified website is needed.
11. Always provide a clear and informative response to the user about the actions prepared and their potential results.
12. If the user request is not a task (e.g., general question), leave the execution field as an empty string.
Always structure your response using the specified AIResponse format.
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct AIResponse {
pub plan: Vec<String>,
pub user_confirmation_required: bool,
// pub confirmation_message: Option<String>,
pub execution: Vec<PlatformSpecificStep>,
pub execution: String,
pub response: String,
}

Expand Down
205 changes: 112 additions & 93 deletions src/components/OsaiApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ const OsaiApp = ({
const { sendMessage, isLoading, abortRequest, executeCode } = useAI();
const messageEndRef = useRef<HTMLDivElement>(null);

useEffect(() => {
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
useEffect(() => {
latestMessagesRef.current = messages;
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
Expand All @@ -46,45 +43,49 @@ const OsaiApp = ({
role: "user",
content: [{ type: "text", text: input }],
};
setMessages((prev) => [
...prev,
newMessage,
{
role: "assistant",
content: [{ type: "text", text: "..." }],
status: "loading",
},
]);
setMessages((prev) => [...prev, newMessage]);
setInput("");
setError("");

try {
// 添加一个临时的 loading 消息
setMessages((prev) => [
...prev,
{
role: "assistant",
content: [{ type: "text", text: "..." }],
status: "loading",
},
]);

const aiResponse: AIResponse = await sendMessage([
...messages,
newMessage,
]);

let executionResult = null;
if (!aiResponse.user_confirmation_required && aiResponse.execution) {
executionResult = await executeCode(aiResponse.execution);
}

const newAssistantMessage: ChatMessage = {
role: "assistant",
content: [{ type: "text", text: aiResponse.response }],
aiResponse,
status: "complete",
executionStatus: aiResponse.user_confirmation_required
? "pending"
: "executing",
: "complete",
executionResult,
};
setMessages((prev) => {
const updatedMessages = [...prev.slice(0, -1), newAssistantMessage];
latestMessagesRef.current = updatedMessages;
if (!aiResponse.user_confirmation_required) {
executeAIResponse(updatedMessages.length - 1);
}
return updatedMessages;
});

// 更新消息,替换 loading 消息
setMessages((prev) => [...prev.slice(0, -1), newAssistantMessage]);
} catch (error: any) {
setMessages((prev) => prev.slice(0, -2));
setInput(newMessage.content[0].text);
setMessages((prev) => prev.slice(0, -2)); // 移除 loading 消息
setInput(input);
setError(
error.message.includes("Open settings and set the API key")
? t("lackApiKey")!
? t("missingApiKey")!
: t("aiResponseError")!
);
console.error("Error getting AI response:", error);
Expand All @@ -93,8 +94,11 @@ const OsaiApp = ({
};

const executeAIResponse = async (messageIndex: number) => {
const message = messages[messageIndex];
if (message.aiResponse && message.aiResponse.should_execute_code) {
console.log("Executing AI response");
const currentMessages = latestMessagesRef.current;
const message = currentMessages[messageIndex];
console.log("Message to execute:", message);
if (message?.aiResponse?.execution) {
setMessages((prev) => {
const newMessages = [...prev];
newMessages[messageIndex] = {
Expand All @@ -104,30 +108,14 @@ const OsaiApp = ({
return newMessages;
});

for (const step of message.aiResponse.execution) {
if (!step.code) continue;
const result = await executeCode(step.code);
setMessages((prev) => {
const newMessages = [...prev];
const executionSteps =
newMessages[messageIndex].aiResponse!.execution;
const stepIndex = executionSteps.findIndex(
(s) => s.step === step.step
);
executionSteps[stepIndex] = {
...step,
status: result.success ? "success" : "failure",
result: result.output,
};
return newMessages;
});
}
const result = await executeCode(message.aiResponse.execution);

setMessages((prev) => {
const newMessages = [...prev];
newMessages[messageIndex] = {
...message,
executionStatus: "complete",
executionResult: result,
};
return newMessages;
});
Expand All @@ -138,26 +126,48 @@ const OsaiApp = ({
messageIndex: number,
confirmed: boolean
) => {
setMessages((prev) => {
const newMessages = [...prev];
newMessages[messageIndex] = {
...newMessages[messageIndex],
executionStatus: confirmed ? "executing" : "rejected",
};
return newMessages;
});

if (confirmed) {
await executeAIResponse(messageIndex);
const message = messages[messageIndex];
if (message.aiResponse?.execution) {
setMessages((prev) => {
const newMessages = [...prev];
newMessages[messageIndex] = {
...message,
executionStatus: "executing",
};
return newMessages;
});

const result = await executeCode(message.aiResponse.execution);

setMessages((prev) => {
const newMessages = [...prev];
newMessages[messageIndex] = {
...message,
executionStatus: "complete",
executionResult: result,
};
return newMessages;
});
}
} else {
setMessages((prev) => {
const newMessages = [...prev];
newMessages[messageIndex] = {
...newMessages[messageIndex],
executionStatus: "rejected",
};
return [...newMessages];
});
}
};

return (
<>
<div className="fixed bottom-4 right-4 transition-all duration-300 ease-in-out w-96 h-[32rem]">
<div className="fixed bottom-4 right-4 transition-all duration-300 ease-in-out w-96 h-full">
<Card className="w-full h-full overflow-hidden shadow-lg">
<CardContent className="flex flex-col h-full p-0">
<div className="flex justify-between items-center p-2 border-b bg-gray-50 dark:bg-gray-800">
<div className="flex justify-between items-center mt-4 p-2 border-b bg-gray-50 dark:bg-gray-800">
<h2 className="text-lg font-bold">Osai</h2>
<div className="flex space-x-2">
<Button
Expand All @@ -182,12 +192,6 @@ const OsaiApp = ({
</div>
</div>
<div className="flex-grow overflow-y-auto p-4 space-y-4">
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{messages.map((msg, index) => (
<div
key={index}
Expand All @@ -209,40 +213,55 @@ const OsaiApp = ({
{msg.role === "user" ? "U" : "AI"}
</AvatarFallback>
</Avatar>
<div
className={`max-w-[70%] p-3 rounded-lg ${
msg.role === "user"
? "bg-blue-500 text-white"
: "bg-gray-200 dark:bg-gray-700 text-black dark:text-white"
}`}
>
{msg.status === "loading" ? "..." : msg.content[0].text}
</div>
</div>
{msg.aiResponse?.user_confirmation_required &&
msg.executionStatus === "pending" && (
<div className="flex justify-center space-x-2 mt-2">
<Button onClick={() => handleConfirmation(index, true)}>
{t("approve")}
</Button>
<Button
onClick={() => handleConfirmation(index, false)}
>
{t("reject")}
</Button>
<div className="max-w-[74%]">
<div
className={` p-3 rounded-lg ${
msg.role === "user"
? "bg-blue-500 text-white"
: "bg-gray-200 dark:bg-gray-700 text-black dark:text-white"
}
${
msg.executionStatus === "rejected" ? "opacity-80" : ""
}
`}
>
{msg.status === "loading" ? "..." : msg.content[0].text}
</div>
)}
{/* {msg.aiResponse?.execution && (
<div className="mt-2">
{msg.aiResponse.execution
.filter((step) => !!step.code)
.map((step, stepIndex) => (
<ExecutionStepComponent key={stepIndex} step={step} />
))}
{msg.aiResponse?.user_confirmation_required &&
msg.executionStatus === "pending" && (
<div className="flex justify-center space-x-2 ">
<Button
className="w-full"
onClick={() => handleConfirmation(index, true)}
>
{t("approve")}
</Button>
<Button
className="w-full"
onClick={() => handleConfirmation(index, false)}
>
{t("reject")}
</Button>
</div>
)}
{msg.executionResult && (
<div className="p-2 bg-gray-100 rounded flex text-sm text-gray-600">
{t("ExecutionResult")}:
{msg.executionResult.success
? t("success")
: t("failure")}
</div>
)}
</div>
)} */}
</div>
</div>
))}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div ref={messageEndRef} />
</div>

Expand Down
4 changes: 2 additions & 2 deletions src/components/SideDrawer .tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const SideDrawer = () => {
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
if (isExpanded) {
await appWindow.setSize(new PhysicalSize(400, 528));
await appWindow.setSize(new PhysicalSize(400, 660));
await appWindow.setPosition(
new PhysicalPosition(screenWidth - 380, screenHeight * 0.4)
new PhysicalPosition(screenWidth - 385, screenHeight * 0.3)
);
} else {
await appWindow.setSize(new PhysicalSize(40, 24));
Expand Down
Loading

0 comments on commit 1cf9232

Please sign in to comment.