mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b53a52f5e9 | |||
| da935f9d8f | |||
| 642cc52a1a | |||
| 4c361ab9e5 | |||
| 5dfa1bb6eb | |||
| a07cf972a5 | |||
| f2e3bc3254 | |||
| 3790ec7d60 | |||
| 3c0719309e | |||
| 0236e0943e | |||
| cd464c0022 | |||
| 4519a7f4f3 | |||
| fdc591330b | |||
| 98e454e82c | |||
| e088d2d24a | |||
| 58c574af1e | |||
| 405f0069f8 | |||
| f26d770d03 | |||
| bf2c3de219 | |||
| 7c35ce16e5 | |||
| f4024ccd94 | |||
| b55bfed831 | |||
| cb0994027f | |||
| bcc9bd0b9a | |||
| 6c144e6b5a | |||
| e90b841b0d | |||
| a1e6ed4dff | |||
| ad6311d3cd |
@@ -0,0 +1,68 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { ChatInput } from "#/components/features/chat/chat-input";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
// Mock TipTapEditor component
|
||||
vi.mock("#/components/features/chat/tiptap-editor", () => ({
|
||||
TipTapEditor: ({ value, onChange, onSubmit, placeholder, disabled, className }: any) => (
|
||||
<div
|
||||
data-testid="mock-tiptap-editor"
|
||||
data-value={value}
|
||||
data-disabled={disabled}
|
||||
data-placeholder={placeholder}
|
||||
className={className}
|
||||
>
|
||||
<button onClick={() => onChange("new value")}>Change</button>
|
||||
<button onClick={() => onSubmit()}>Submit</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("ChatInput with TipTap", () => {
|
||||
it("renders the TipTap editor", () => {
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
render(
|
||||
<ChatInput
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("mock-tiptap-editor")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("passes the correct props to TipTapEditor", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(
|
||||
<ChatInput
|
||||
value="Test value"
|
||||
onSubmit={onSubmit}
|
||||
onChange={onChange}
|
||||
disabled={true}
|
||||
className="test-class"
|
||||
/>
|
||||
);
|
||||
|
||||
const editor = screen.getByTestId("mock-tiptap-editor");
|
||||
expect(editor).toHaveAttribute("data-value", "Test value");
|
||||
expect(editor).toHaveAttribute("data-disabled", "true");
|
||||
});
|
||||
|
||||
it("handles submit button click", () => {
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
render(
|
||||
<ChatInput
|
||||
value="Test value"
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
);
|
||||
|
||||
// Click the submit button
|
||||
screen.getByText("Submit").click();
|
||||
|
||||
expect(onSubmit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { MicroagentSuggestions, MicroagentInfo } from "#/components/features/chat/microagent-suggestions";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
describe("MicroagentSuggestions", () => {
|
||||
const mockMicroagents: MicroagentInfo[] = [
|
||||
{
|
||||
name: "PR Update",
|
||||
trigger: "/pr_update",
|
||||
description: "Update a pull request",
|
||||
},
|
||||
{
|
||||
name: "PR Comment",
|
||||
trigger: "/pr_comment",
|
||||
description: "Comment on a pull request",
|
||||
},
|
||||
{
|
||||
name: "Test Update",
|
||||
trigger: "/update_test",
|
||||
description: "Update tests",
|
||||
},
|
||||
];
|
||||
|
||||
// Mock fetch
|
||||
beforeEach(() => {
|
||||
global.fetch = vi.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockMicroagents),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should render microagent suggestions when visible", async () => {
|
||||
const onSelect = vi.fn();
|
||||
|
||||
render(
|
||||
<MicroagentSuggestions
|
||||
query="/"
|
||||
isVisible={true}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
// Wait for the fetch to complete
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText("/pr_update")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText("/pr_comment")).toBeInTheDocument();
|
||||
expect(screen.getByText("/update_test")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should filter microagents based on query", async () => {
|
||||
const onSelect = vi.fn();
|
||||
|
||||
const { rerender } = render(
|
||||
<MicroagentSuggestions
|
||||
query="/"
|
||||
isVisible={true}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
// Wait for the fetch to complete
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText("/pr_update")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Rerender with a filtered query
|
||||
rerender(
|
||||
<MicroagentSuggestions
|
||||
query="/pr"
|
||||
isVisible={true}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText("/pr_update")).toBeInTheDocument();
|
||||
expect(screen.getByText("/pr_comment")).toBeInTheDocument();
|
||||
expect(screen.queryByText("/update_test")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onSelect when a microagent is clicked", async () => {
|
||||
const onSelect = vi.fn();
|
||||
|
||||
render(
|
||||
<MicroagentSuggestions
|
||||
query="/"
|
||||
isVisible={true}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
// Wait for the fetch to complete
|
||||
await vi.waitFor(() => {
|
||||
expect(screen.getByText("/pr_update")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click on a microagent
|
||||
fireEvent.click(screen.getByText("/pr_update"));
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith("/pr_update");
|
||||
});
|
||||
|
||||
it("should not render when isVisible is false", () => {
|
||||
const onSelect = vi.fn();
|
||||
|
||||
render(
|
||||
<MicroagentSuggestions
|
||||
query="/"
|
||||
isVisible={false}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByText("Loading microagents...")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import { TipTapEditor } from "#/components/features/chat/tiptap-editor";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
// Add a custom query to find elements by attribute
|
||||
screen.getByAttribute = (attribute: string, value: string) => {
|
||||
return document.querySelector(`[${attribute}="${value}"]`);
|
||||
};
|
||||
|
||||
// Mock fetch for microagents
|
||||
global.fetch = vi.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve([
|
||||
{
|
||||
name: "PR Update",
|
||||
trigger: "/pr_update",
|
||||
description: "Update a pull request",
|
||||
},
|
||||
{
|
||||
name: "PR Comment",
|
||||
trigger: "/pr_comment",
|
||||
description: "Comment on a pull request",
|
||||
},
|
||||
]),
|
||||
})
|
||||
);
|
||||
|
||||
describe("TipTapEditor", () => {
|
||||
it("renders the editor", () => {
|
||||
const onChange = vi.fn();
|
||||
const onSubmit = vi.fn();
|
||||
|
||||
render(
|
||||
<TipTapEditor
|
||||
value=""
|
||||
onChange={onChange}
|
||||
onSubmit={onSubmit}
|
||||
placeholder="Test placeholder"
|
||||
/>
|
||||
);
|
||||
|
||||
// Check that the editor is rendered
|
||||
const editorElement = screen.getByAttribute("contenteditable", "true");
|
||||
expect(editorElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("passes the correct props", () => {
|
||||
const onChange = vi.fn();
|
||||
const onSubmit = vi.fn();
|
||||
const onFocus = vi.fn();
|
||||
const onBlur = vi.fn();
|
||||
|
||||
render(
|
||||
<TipTapEditor
|
||||
value="Test value"
|
||||
onChange={onChange}
|
||||
onSubmit={onSubmit}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
placeholder="Test placeholder"
|
||||
disabled={true}
|
||||
className="test-class"
|
||||
/>
|
||||
);
|
||||
|
||||
// Check that the editor has the correct class
|
||||
const editorElement = screen.getByAttribute("contenteditable", "true");
|
||||
expect(editorElement?.classList.contains("test-class")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Generated
+759
-3
@@ -18,6 +18,11 @@
|
||||
"@stripe/react-stripe-js": "^3.6.0",
|
||||
"@stripe/stripe-js": "^7.2.0",
|
||||
"@tanstack/react-query": "^5.75.1",
|
||||
"@tiptap/extension-mention": "^2.12.0",
|
||||
"@tiptap/extension-placeholder": "^2.12.0",
|
||||
"@tiptap/pm": "^2.12.0",
|
||||
"@tiptap/react": "^2.12.0",
|
||||
"@tiptap/starter-kit": "^2.12.0",
|
||||
"@vitejs/plugin-react": "^4.4.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
@@ -3591,6 +3596,16 @@
|
||||
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/breadcrumbs": {
|
||||
"version": "3.5.23",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.23.tgz",
|
||||
@@ -5156,6 +5171,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@remirror/core-constants": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
|
||||
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
|
||||
@@ -5937,6 +5958,435 @@
|
||||
"@testing-library/dom": ">=7.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.12.0.tgz",
|
||||
"integrity": "sha512-3qX8oGVKFFZzQ0vit+ZolR6AJIATBzmEmjAA0llFhWk4vf3v64p1YcXcJsOBsr5scizJu5L6RYWEFatFwqckRg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-blockquote": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.12.0.tgz",
|
||||
"integrity": "sha512-XUC2A77YAPMJS2SqZ2S62IGcUH8gZ7cdhoWlYQb1pR4ZzXFByeKDJPxfYeAePSiuI01YGrlzgY2c6Ncx/DtO0A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bold": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.12.0.tgz",
|
||||
"integrity": "sha512-lAUtoLDLRc5ofD2I9MFY6MQ7d1qBLLqS1rvpwaPjOaoQb/GPVnaHj9qXYG0SY9K3erMtto48bMFpAcscjZHzZQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bubble-menu": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.12.0.tgz",
|
||||
"integrity": "sha512-DYijoE0igV0Oi+ZppFsp2UrQsM/4HZtmmpD78BJM9zfCbd1YvAUIxmzmXr8uqU18OHd1uQy+/zvuNoUNYyw67g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tippy.js": "^6.3.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bullet-list": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.12.0.tgz",
|
||||
"integrity": "sha512-YTCjztB8MaIpwyxFYr81H4+LdKCq1VlaSXQyrPdB44mVdhhRqc46BYQb8/B//XE3UIu3X2QWFjwrqRlUq6vUiw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.12.0.tgz",
|
||||
"integrity": "sha512-R7RaS+hJeHFim7alImQ9L9CSWSMjWXvz0Ote568x9ea5gdBGUYW8PcH+5a91lh8e1XGYWBM12a8oJZRyxg/tQA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code-block": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.12.0.tgz",
|
||||
"integrity": "sha512-1D7cYAjgxEFHdfC/35Ooi4GqWKB5sszbW8iI7N16XILNln26xb0d5KflXqYrwr9CN/ZnZoCl2o6YsP7xEObcZA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-document": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.12.0.tgz",
|
||||
"integrity": "sha512-sA1Q+mxDIv0Y3qQTBkYGwknNbDcGFiJ/fyAFholXpqbrcRx3GavwR/o0chBdsJZlFht0x7AWGwUYWvIo7wYilA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-dropcursor": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.12.0.tgz",
|
||||
"integrity": "sha512-zcZSOXFj+7LVnmdPWTfKr5AoxYIzFPFlLJe35AdTQC5IhkljLn1Exct8I30ZREojX/00hKYsO7JJmePS6TEVlQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-floating-menu": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.12.0.tgz",
|
||||
"integrity": "sha512-BYpyZx/56KCDksWuJJbhki/uNgt9sACuSSZFH5AN1yS1ISD+EzIxqf6Pzzv8QCoNJ+KcRNVaZsOlOFaJGoyzag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tippy.js": "^6.3.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-gapcursor": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.12.0.tgz",
|
||||
"integrity": "sha512-k8ji5v9YKn7bNjo8UtI9hEfXfl4tKUp1hpJOEmUxGJQa3LIwrwSbReupUTnHszGQelzxikS/l1xO9P0TIGwRoA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-hard-break": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.12.0.tgz",
|
||||
"integrity": "sha512-08MNS2PK5DzdnAfqXn4krmJ/xebKmWpRpYqqN5EM8AvetYKlAJyTVSpo0ZUeGbZ3EZiPm9djgSnrLqpFUDjRCg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-heading": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.12.0.tgz",
|
||||
"integrity": "sha512-9DfES4Wd5TX1foI70N9sAL+35NN1UHrtzDYN2+dTHupnmKir9RaMXyZcbkUb4aDVzYrGxIqxJzHBVkquKIlTrw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-history": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.12.0.tgz",
|
||||
"integrity": "sha512-+B9CAf2BFURC6mQiM1OQtahVTzdEOEgT/UUNlRZkeeBc0K5of3dr6UdBqaoaMAefja3jx5PqiQ7mhUBAjSt6AA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-horizontal-rule": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.12.0.tgz",
|
||||
"integrity": "sha512-Vi2+6RIehDSpoJn/7PDuOieUj7W7WrEb4wBxK9TG8PDscihR0mehhhzm/K2xhH4TN48iPJGRsjDFrFjTbXmcnw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-italic": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.12.0.tgz",
|
||||
"integrity": "sha512-JKcXK3LmEsmxNzEq5e06rPUGMRLUxmJ2mYtBY4NlJ6yLM9XMDljtgeTnWT0ySLYmfINSFTkX4S7WIRbpl9l4pw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-list-item": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.12.0.tgz",
|
||||
"integrity": "sha512-4YwZooC8HP+gPxs6YrkB1ayggyYbgVvJx/rWBT6lKSW2MVVg8QXi1zAcSI3MhIhHmqDysXXFPL8JURlbeGjaFA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-mention": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.12.0.tgz",
|
||||
"integrity": "sha512-+b/fqOU+pRWWAo0ZfyInkhkvV0Ub5RpNrYZ45v2nn5PjbXbxyxNQ51zT6cGk2F6Jmc6UBmlR8iqqNTIQY9ieEg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0",
|
||||
"@tiptap/suggestion": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-ordered-list": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.12.0.tgz",
|
||||
"integrity": "sha512-1ys0e/oqk09oXxrB1WzAx5EntK/QreObG/V1yhgihGm429fxHMsxzIYN6dKAYxx0YOPQG7qEZRrrPuWU70Ms7g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-paragraph": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.12.0.tgz",
|
||||
"integrity": "sha512-QNK5cgewCunWFxpLlbvvoO1rrLgEtNKxiY79fctP9toV+e59R+1i1Q9lXC1O5mOfDgVxCb6uFDMsqmKhFjpPog==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-placeholder": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.12.0.tgz",
|
||||
"integrity": "sha512-K7irDox4P+NLAMjVrJeG72f0sulsCRYpx1Cy4gxKCdi1LTivj5VkXa6MXmi42KTCwBu3pWajBctYIOAES1FTAA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-strike": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.12.0.tgz",
|
||||
"integrity": "sha512-nBaa5YtBsLJPZFfSs36sBz4Zgi/c8b3MsmS/Az8uXaHb0R9yPewOVUMDIQbxMct8SXUlIo9VtKlOL+mVJ3Nkpw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.12.0.tgz",
|
||||
"integrity": "sha512-0ytN9V1tZYTXdiYDQg4FB2SQ56JAJC9r/65snefb9ztl+gZzDrIvih7CflHs1ic9PgyjexfMLeH+VzuMccNyZw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text-style": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.12.0.tgz",
|
||||
"integrity": "sha512-Pxwt23ZlvbQUahV0PvHy8Ej6IAuKR1FvHobUvwP3T8AiY7hob66fWRe7tQbESzSAzm5Vv2xkvyHeU8vekMTezA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/pm": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.12.0.tgz",
|
||||
"integrity": "sha512-TNzVwpeNzFfHAcYTOKqX9iU4fRxliyoZrCnERR+RRzeg7gWrXrCLubQt1WEx0sojMAfznshSL3M5HGsYjEbYwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-changeset": "^2.3.0",
|
||||
"prosemirror-collab": "^1.3.1",
|
||||
"prosemirror-commands": "^1.6.2",
|
||||
"prosemirror-dropcursor": "^1.8.1",
|
||||
"prosemirror-gapcursor": "^1.3.2",
|
||||
"prosemirror-history": "^1.4.1",
|
||||
"prosemirror-inputrules": "^1.4.0",
|
||||
"prosemirror-keymap": "^1.2.2",
|
||||
"prosemirror-markdown": "^1.13.1",
|
||||
"prosemirror-menu": "^1.2.4",
|
||||
"prosemirror-model": "^1.23.0",
|
||||
"prosemirror-schema-basic": "^1.2.3",
|
||||
"prosemirror-schema-list": "^1.4.1",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-tables": "^1.6.4",
|
||||
"prosemirror-trailing-node": "^3.0.0",
|
||||
"prosemirror-transform": "^1.10.2",
|
||||
"prosemirror-view": "^1.37.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/react": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.12.0.tgz",
|
||||
"integrity": "sha512-D+PR+4kJO9h8AB/7XyQ/Anw8tqeS2ecv5QemBOCHi9JlMAjytauUrj6IfFBO9RbsCowlBjW5GnSpFhzpk2Gghg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tiptap/extension-bubble-menu": "^2.12.0",
|
||||
"@tiptap/extension-floating-menu": "^2.12.0",
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"fast-deep-equal": "^3",
|
||||
"use-sync-external-store": "^1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/starter-kit": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.12.0.tgz",
|
||||
"integrity": "sha512-wlcEEtexd6u0gbR311/OFZnbtRWU97DUsY6/GsSQzN4rqZ7Ra6YbfHEN5Lutu+I/anomK8vKy8k9NyvfY5Hllg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^2.12.0",
|
||||
"@tiptap/extension-blockquote": "^2.12.0",
|
||||
"@tiptap/extension-bold": "^2.12.0",
|
||||
"@tiptap/extension-bullet-list": "^2.12.0",
|
||||
"@tiptap/extension-code": "^2.12.0",
|
||||
"@tiptap/extension-code-block": "^2.12.0",
|
||||
"@tiptap/extension-document": "^2.12.0",
|
||||
"@tiptap/extension-dropcursor": "^2.12.0",
|
||||
"@tiptap/extension-gapcursor": "^2.12.0",
|
||||
"@tiptap/extension-hard-break": "^2.12.0",
|
||||
"@tiptap/extension-heading": "^2.12.0",
|
||||
"@tiptap/extension-history": "^2.12.0",
|
||||
"@tiptap/extension-horizontal-rule": "^2.12.0",
|
||||
"@tiptap/extension-italic": "^2.12.0",
|
||||
"@tiptap/extension-list-item": "^2.12.0",
|
||||
"@tiptap/extension-ordered-list": "^2.12.0",
|
||||
"@tiptap/extension-paragraph": "^2.12.0",
|
||||
"@tiptap/extension-strike": "^2.12.0",
|
||||
"@tiptap/extension-text": "^2.12.0",
|
||||
"@tiptap/extension-text-style": "^2.12.0",
|
||||
"@tiptap/pm": "^2.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/suggestion": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.12.0.tgz",
|
||||
"integrity": "sha512-bsXLoZbjUo1oOF1Z+XSfoGzbcnrTcYtJdfylM/FerMLU9T12dhsM/Ri2SKLX4IR5D0HJ07FcsEHCrGEy8Y5y0A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.7.0",
|
||||
"@tiptap/pm": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
@@ -6038,6 +6488,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
|
||||
@@ -6053,6 +6509,16 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "14.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "^5",
|
||||
"@types/mdurl": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
|
||||
@@ -6062,6 +6528,12 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
@@ -6856,7 +7328,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
@@ -7964,6 +8435,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
@@ -8747,7 +9224,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -9554,7 +10030,6 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
@@ -11580,6 +12055,15 @@
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "15.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.1.tgz",
|
||||
@@ -12040,6 +12524,35 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-table": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
||||
@@ -12341,6 +12854,12 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -13647,6 +14166,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/orderedmap": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
|
||||
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/outvariant": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
|
||||
@@ -14308,6 +14833,201 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-changeset": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.0.tgz",
|
||||
"integrity": "sha512-8wRKhlEwEJ4I13Ju54q2NZR1pVKGTgJ/8XsQ8L5A5uUsQ/YQScQJuEAuh8Bn8i6IwAMjjLRABd9lVli+DlIiVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-transform": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-collab": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
|
||||
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-commands": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
|
||||
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.10.2"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dropcursor": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
|
||||
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.1.0",
|
||||
"prosemirror-view": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-gapcursor": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz",
|
||||
"integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-keymap": "^1.0.0",
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-view": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-history": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz",
|
||||
"integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.2.2",
|
||||
"prosemirror-transform": "^1.0.0",
|
||||
"prosemirror-view": "^1.31.0",
|
||||
"rope-sequence": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-inputrules": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.0.tgz",
|
||||
"integrity": "sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-keymap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
|
||||
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"w3c-keyname": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-markdown": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz",
|
||||
"integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/markdown-it": "^14.0.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"prosemirror-model": "^1.25.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-menu": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz",
|
||||
"integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"crelt": "^1.0.0",
|
||||
"prosemirror-commands": "^1.0.0",
|
||||
"prosemirror-history": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-model": {
|
||||
"version": "1.25.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.1.tgz",
|
||||
"integrity": "sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"orderedmap": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-schema-basic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
|
||||
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.25.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-schema-list": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
|
||||
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-state": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
|
||||
"integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-transform": "^1.0.0",
|
||||
"prosemirror-view": "^1.27.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-tables": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.7.1.tgz",
|
||||
"integrity": "sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-keymap": "^1.2.2",
|
||||
"prosemirror-model": "^1.25.0",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-transform": "^1.10.3",
|
||||
"prosemirror-view": "^1.39.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-trailing-node": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
|
||||
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remirror/core-constants": "3.0.0",
|
||||
"escape-string-regexp": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prosemirror-model": "^1.22.1",
|
||||
"prosemirror-state": "^1.4.2",
|
||||
"prosemirror-view": "^1.33.8"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-transform": {
|
||||
"version": "1.10.4",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz",
|
||||
"integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-view": {
|
||||
"version": "1.39.2",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.39.2.tgz",
|
||||
"integrity": "sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.20.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@@ -14350,6 +15070,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
@@ -15170,6 +15899,12 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rope-sequence": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
|
||||
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rrweb-cssom": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
|
||||
@@ -16536,6 +17271,15 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tippy.js": {
|
||||
"version": "6.3.7",
|
||||
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
|
||||
"integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
||||
@@ -16841,6 +17585,12 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||
@@ -17457,6 +18207,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
"@stripe/react-stripe-js": "^3.6.0",
|
||||
"@stripe/stripe-js": "^7.2.0",
|
||||
"@tanstack/react-query": "^5.75.1",
|
||||
"@tiptap/extension-mention": "^2.12.0",
|
||||
"@tiptap/extension-placeholder": "^2.12.0",
|
||||
"@tiptap/pm": "^2.12.0",
|
||||
"@tiptap/react": "^2.12.0",
|
||||
"@tiptap/starter-kit": "^2.12.0",
|
||||
"@vitejs/plugin-react": "^4.4.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import TextareaAutosize from "react-textarea-autosize";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { SubmitButton } from "#/components/shared/buttons/submit-button";
|
||||
import { StopButton } from "#/components/shared/buttons/stop-button";
|
||||
import { TipTapEditor } from "./tiptap-editor";
|
||||
|
||||
interface ChatInputProps {
|
||||
name?: string;
|
||||
@@ -28,7 +28,7 @@ export function ChatInput({
|
||||
button = "submit",
|
||||
disabled,
|
||||
showButton = true,
|
||||
value,
|
||||
value = "",
|
||||
maxRows = 16,
|
||||
onSubmit,
|
||||
onStop,
|
||||
@@ -40,105 +40,35 @@ export function ChatInput({
|
||||
buttonClassName,
|
||||
}: ChatInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
||||
const [isDraggingOver, setIsDraggingOver] = React.useState(false);
|
||||
const [inputValue, setInputValue] = React.useState(value);
|
||||
|
||||
const handlePaste = (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
// Only handle paste if we have an image paste handler and there are files
|
||||
if (onImagePaste && event.clipboardData.files.length > 0) {
|
||||
const files = Array.from(event.clipboardData.files).filter((file) =>
|
||||
file.type.startsWith("image/"),
|
||||
);
|
||||
// Only prevent default if we found image files to handle
|
||||
if (files.length > 0) {
|
||||
event.preventDefault();
|
||||
onImagePaste(files);
|
||||
}
|
||||
}
|
||||
// For text paste, let the default behavior handle it
|
||||
};
|
||||
|
||||
const handleDragOver = (event: React.DragEvent<HTMLTextAreaElement>) => {
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer.types.includes("Files")) {
|
||||
setIsDraggingOver(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragLeave = (event: React.DragEvent<HTMLTextAreaElement>) => {
|
||||
event.preventDefault();
|
||||
setIsDraggingOver(false);
|
||||
};
|
||||
|
||||
const handleDrop = (event: React.DragEvent<HTMLTextAreaElement>) => {
|
||||
event.preventDefault();
|
||||
setIsDraggingOver(false);
|
||||
if (onImagePaste && event.dataTransfer.files.length > 0) {
|
||||
const files = Array.from(event.dataTransfer.files).filter((file) =>
|
||||
file.type.startsWith("image/"),
|
||||
);
|
||||
if (files.length > 0) {
|
||||
onImagePaste(files);
|
||||
}
|
||||
}
|
||||
const handleChange = (newValue: string) => {
|
||||
setInputValue(newValue);
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
const handleSubmitMessage = () => {
|
||||
const message = value || textareaRef.current?.value || "";
|
||||
if (message.trim()) {
|
||||
onSubmit(message);
|
||||
if (inputValue.trim()) {
|
||||
onSubmit(inputValue);
|
||||
setInputValue("");
|
||||
onChange?.("");
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.value = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (
|
||||
event.key === "Enter" &&
|
||||
!event.shiftKey &&
|
||||
!disabled &&
|
||||
!event.nativeEvent.isComposing
|
||||
) {
|
||||
event.preventDefault();
|
||||
handleSubmitMessage();
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
onChange?.(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="chat-input"
|
||||
className="flex items-end justify-end grow gap-1 min-h-6 w-full"
|
||||
className="flex items-end justify-end grow gap-1 min-h-6 w-full relative"
|
||||
>
|
||||
<TextareaAutosize
|
||||
ref={textareaRef}
|
||||
name={name}
|
||||
placeholder={t(I18nKey.SUGGESTIONS$WHAT_TO_BUILD)}
|
||||
onKeyDown={handleKeyPress}
|
||||
<TipTapEditor
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmitMessage}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onPaste={handlePaste}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
value={value}
|
||||
minRows={1}
|
||||
maxRows={maxRows}
|
||||
data-dragging-over={isDraggingOver}
|
||||
className={cn(
|
||||
"grow text-sm self-center placeholder:text-neutral-400 text-white resize-none outline-none ring-0",
|
||||
"transition-all duration-200 ease-in-out",
|
||||
isDraggingOver
|
||||
? "bg-neutral-600/50 rounded-lg px-2"
|
||||
: "bg-transparent",
|
||||
className,
|
||||
)}
|
||||
placeholder={t(I18nKey.SUGGESTIONS$WHAT_TO_BUILD)}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
/>
|
||||
{showButton && (
|
||||
<div className={buttonClassName}>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
export interface MicroagentInfo {
|
||||
name: string;
|
||||
trigger: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface MicroagentSuggestionsProps {
|
||||
query: string;
|
||||
isVisible: boolean;
|
||||
onSelect: (trigger: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function MicroagentSuggestions({
|
||||
query,
|
||||
isVisible,
|
||||
onSelect,
|
||||
className,
|
||||
}: MicroagentSuggestionsProps) {
|
||||
const [microagents, setMicroagents] = useState<MicroagentInfo[]>([]);
|
||||
const [filteredMicroagents, setFilteredMicroagents] = useState<
|
||||
MicroagentInfo[]
|
||||
>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Fetch microagents when the component mounts
|
||||
useEffect(() => {
|
||||
const fetchMicroagents = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch("/api/options/microagents");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setMicroagents(data);
|
||||
}
|
||||
} catch (error) {
|
||||
// Log error silently
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMicroagents();
|
||||
}, []);
|
||||
|
||||
// Filter microagents based on the query
|
||||
useEffect(() => {
|
||||
if (!query || query === "/") {
|
||||
setFilteredMicroagents(microagents);
|
||||
} else {
|
||||
const searchTerm = query.slice(1).toLowerCase();
|
||||
const filtered = microagents.filter(
|
||||
(agent) =>
|
||||
agent.trigger.toLowerCase().includes(searchTerm) ||
|
||||
agent.name.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
setFilteredMicroagents(filtered);
|
||||
}
|
||||
}, [query, microagents]);
|
||||
|
||||
if (!isVisible || (filteredMicroagents.length === 0 && !loading)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute bottom-full left-0 w-full max-h-60 overflow-y-auto bg-neutral-800 rounded-md shadow-lg z-10 mb-1 border border-neutral-600",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{loading && (
|
||||
<div className="p-2 text-neutral-400">Loading microagents...</div>
|
||||
)}
|
||||
{!loading && filteredMicroagents.length === 0 && (
|
||||
<div className="p-2 text-neutral-400">No microagents found</div>
|
||||
)}
|
||||
{!loading && filteredMicroagents.length > 0 && (
|
||||
<ul className="py-1">
|
||||
{filteredMicroagents.map((agent) => (
|
||||
<div
|
||||
key={agent.trigger}
|
||||
className="px-3 py-2 hover:bg-neutral-700 cursor-pointer flex flex-col"
|
||||
onClick={() => onSelect(agent.trigger)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
onSelect(agent.trigger);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="font-medium text-white">{agent.trigger}</span>
|
||||
<span className="text-xs text-neutral-400 truncate">
|
||||
{agent.description}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
.ProseMirror {
|
||||
padding: 0.5rem;
|
||||
min-height: 2rem;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.ProseMirror p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ProseMirror:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.microagent-mention {
|
||||
color: #3b82f6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.microagent-suggestions-popup {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* Placeholder styling */
|
||||
.ProseMirror p.is-editor-empty:first-child::before {
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
color: #9ca3af;
|
||||
pointer-events: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Dropdown styling */
|
||||
.microagent-suggestions-popup .bg-neutral-800 {
|
||||
background-color: #262626;
|
||||
border-color: #525252;
|
||||
}
|
||||
|
||||
.microagent-suggestions-popup .hover\:bg-neutral-700:hover,
|
||||
.microagent-suggestions-popup .bg-neutral-700 {
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
.microagent-suggestions-popup .text-white {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.microagent-suggestions-popup .text-neutral-400 {
|
||||
color: #a3a3a3;
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import React, { useEffect, useCallback, useState } from 'react';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { Mention } from '@tiptap/extension-mention';
|
||||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { cn } from "#/utils/utils";
|
||||
import './tiptap-editor.css';
|
||||
|
||||
export interface MicroagentInfo {
|
||||
name: string;
|
||||
trigger: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface TipTapEditorProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onSubmit: (value: string) => void;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TipTapEditor({
|
||||
value,
|
||||
onChange,
|
||||
onSubmit,
|
||||
onFocus,
|
||||
onBlur,
|
||||
placeholder = 'What would you like to build?',
|
||||
disabled = false,
|
||||
className,
|
||||
}: TipTapEditorProps) {
|
||||
const [microagents, setMicroagents] = useState<MicroagentInfo[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Fetch microagents when the component mounts
|
||||
useEffect(() => {
|
||||
const fetchMicroagents = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch("/api/options/microagents");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setMicroagents(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching microagents:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMicroagents();
|
||||
}, []);
|
||||
|
||||
// Custom suggestion handler for microagents
|
||||
const suggestionHandler = useCallback(() => {
|
||||
return {
|
||||
char: '/',
|
||||
items: ({ query }: { query: string }) => {
|
||||
if (!query) return microagents;
|
||||
|
||||
return microagents.filter(item =>
|
||||
item.trigger.toLowerCase().includes(query.toLowerCase()) ||
|
||||
item.name.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
},
|
||||
render: () => {
|
||||
let popup: HTMLElement | null = null;
|
||||
let component: React.ReactNode | null = null;
|
||||
|
||||
return {
|
||||
onStart: (props: any) => {
|
||||
popup = document.createElement('div');
|
||||
popup.classList.add('microagent-suggestions-popup');
|
||||
document.body.appendChild(popup);
|
||||
|
||||
component = (
|
||||
<div className="absolute z-50 bg-neutral-800 rounded-md shadow-lg border border-neutral-600 w-64 max-h-60 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="p-2 text-neutral-400">Loading microagents...</div>
|
||||
) : props.items.length === 0 ? (
|
||||
<div className="p-2 text-neutral-400">No microagents found</div>
|
||||
) : (
|
||||
<ul className="py-1">
|
||||
{props.items.map((item: MicroagentInfo, index: number) => (
|
||||
<div
|
||||
key={item.trigger}
|
||||
className={cn(
|
||||
"px-3 py-2 hover:bg-neutral-700 cursor-pointer flex flex-col",
|
||||
index === props.selectedIndex ? "bg-neutral-700" : ""
|
||||
)}
|
||||
onClick={() => props.command(item)}
|
||||
>
|
||||
<span className="font-medium text-white">{item.trigger}</span>
|
||||
<span className="text-xs text-neutral-400 truncate">
|
||||
{item.description}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (popup) {
|
||||
const { view, clientRect } = props;
|
||||
const { top, left } = clientRect();
|
||||
|
||||
// Position the popup
|
||||
popup.style.position = 'absolute';
|
||||
popup.style.top = `${top}px`;
|
||||
popup.style.left = `${left}px`;
|
||||
|
||||
// Render the component into the popup
|
||||
const root = createRoot(popup);
|
||||
root.render(component);
|
||||
}
|
||||
},
|
||||
onUpdate: (props: any) => {
|
||||
if (popup) {
|
||||
const { clientRect } = props;
|
||||
const { top, left } = clientRect();
|
||||
|
||||
// Update position
|
||||
popup.style.top = `${top}px`;
|
||||
popup.style.left = `${left}px`;
|
||||
|
||||
// Re-render with updated props
|
||||
const root = createRoot(popup);
|
||||
root.render(component);
|
||||
}
|
||||
},
|
||||
onKeyDown: (props: any) => {
|
||||
if (props.event.key === 'Escape') {
|
||||
props.event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
onExit: () => {
|
||||
if (popup) {
|
||||
document.body.removeChild(popup);
|
||||
popup = null;
|
||||
component = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
command: ({ editor, range, props }: any) => {
|
||||
// Insert the selected microagent trigger
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.insertContent(props.trigger.replace('/', '') + ' ')
|
||||
.run();
|
||||
},
|
||||
};
|
||||
}, [microagents, loading]);
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Placeholder.configure({
|
||||
placeholder,
|
||||
emptyEditorClass: 'is-editor-empty',
|
||||
}),
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'microagent-mention',
|
||||
},
|
||||
suggestion: suggestionHandler(),
|
||||
renderLabel: ({ node }) => node.attrs.label,
|
||||
}),
|
||||
],
|
||||
content: value,
|
||||
onUpdate: ({ editor }) => {
|
||||
onChange(editor.getText());
|
||||
},
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: cn(
|
||||
'prose prose-sm focus:outline-none w-full max-w-full',
|
||||
'text-white placeholder:text-neutral-400',
|
||||
className
|
||||
),
|
||||
},
|
||||
handleKeyDown: (view, event) => {
|
||||
// Handle Enter key for submission
|
||||
if (event.key === 'Enter' && !event.shiftKey && !disabled) {
|
||||
event.preventDefault();
|
||||
const text = view.state.doc.textContent;
|
||||
if (text.trim()) {
|
||||
onSubmit(text);
|
||||
// Clear the editor
|
||||
view.dispatch(view.state.tr.delete(0, view.state.doc.content.size));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Update editor content when value prop changes
|
||||
useEffect(() => {
|
||||
if (editor && editor.getText() !== value) {
|
||||
editor.commands.setContent(value);
|
||||
}
|
||||
}, [value, editor]);
|
||||
|
||||
// Handle focus and blur events
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
|
||||
const handleFocus = () => {
|
||||
if (onFocus) onFocus();
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
if (onBlur) onBlur();
|
||||
};
|
||||
|
||||
editor.on('focus', handleFocus);
|
||||
editor.on('blur', handleBlur);
|
||||
|
||||
return () => {
|
||||
editor.off('focus', handleFocus);
|
||||
editor.off('blur', handleBlur);
|
||||
};
|
||||
}, [editor, onFocus, onBlur]);
|
||||
|
||||
return (
|
||||
<EditorContent
|
||||
editor={editor}
|
||||
className={cn(
|
||||
"grow text-sm self-center resize-none outline-none ring-0",
|
||||
"transition-all duration-200 ease-in-out",
|
||||
"bg-transparent",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -103,6 +103,26 @@ const openHandsHandlers = [
|
||||
HttpResponse.json(["mock-invariant"]),
|
||||
),
|
||||
|
||||
http.get("/api/options/microagents", async () =>
|
||||
HttpResponse.json([
|
||||
{
|
||||
name: "PR Update",
|
||||
trigger: "/pr_update",
|
||||
description: "Update a pull request",
|
||||
},
|
||||
{
|
||||
name: "PR Comment",
|
||||
trigger: "/pr_comment",
|
||||
description: "Comment on a pull request",
|
||||
},
|
||||
{
|
||||
name: "Test Update",
|
||||
trigger: "/update_test",
|
||||
description: "Update tests",
|
||||
},
|
||||
]),
|
||||
),
|
||||
|
||||
http.post("http://localhost:3001/api/submit-feedback", async () => {
|
||||
await delay(1200);
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: add_repo_inst
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /add_repo_inst
|
||||
inputs:
|
||||
- name: REPO_FOLDER_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
---
|
||||
|
||||
Please browse the current repository under /workspace/{{ REPO_FOLDER_NAME }}, look at the documentation and relevant code, and understand the purpose of this repository.
|
||||
|
||||
Specifically, I want you to create a `.openhands/microagents/repo.md` file. This file should contain succinct information that summarizes (1) the purpose of this repository, (2) the general setup of this repo, and (3) a brief description of the structure of this repo.
|
||||
|
||||
Here's an example:
|
||||
```markdown
|
||||
---
|
||||
name: repo
|
||||
type: repo
|
||||
agent: CodeActAgent
|
||||
---
|
||||
|
||||
This repository contains the code for OpenHands, an automated AI software engineer. It has a Python backend
|
||||
(in the `openhands` directory) and React frontend (in the `frontend` directory).
|
||||
|
||||
## General Setup:
|
||||
To set up the entire repo, including frontend and backend, run `make build`.
|
||||
You don't need to do this unless the user asks you to, or if you're trying to run the entire application.
|
||||
|
||||
Before pushing any changes, you should ensure that any lint errors or simple test errors have been fixed.
|
||||
|
||||
* If you've made changes to the backend, you should run `pre-commit run --all-files --config ./dev_config/python/.pre-commit-config.yaml`
|
||||
* If you've made changes to the frontend, you should run `cd frontend && npm run lint:fix && npm run build ; cd ..`
|
||||
|
||||
If either command fails, it may have automatically fixed some issues. You should fix any issues that weren't automatically fixed,
|
||||
then re-run the command to ensure it passes.
|
||||
|
||||
## Repository Structure
|
||||
Backend:
|
||||
- Located in the `openhands` directory
|
||||
- Testing:
|
||||
- All tests are in `tests/unit/test_*.py`
|
||||
- To test new code, run `poetry run pytest tests/unit/test_xxx.py` where `xxx` is the appropriate file for the current functionality
|
||||
- Write all tests with pytest
|
||||
|
||||
Frontend:
|
||||
- Located in the `frontend` directory
|
||||
- Prerequisites: A recent version of NodeJS / NPM
|
||||
- Setup: Run `npm install` in the frontend directory
|
||||
- Testing:
|
||||
- Run tests: `npm run test`
|
||||
- To run specific tests: `npm run test -- -t "TestName"`
|
||||
- Building:
|
||||
- Build for production: `npm run build`
|
||||
- Environment Variables:
|
||||
- Set in `frontend/.env` or as environment variables
|
||||
- Available variables: VITE_BACKEND_HOST, VITE_USE_TLS, VITE_INSECURE_SKIP_VERIFY, VITE_FRONTEND_PORT
|
||||
- Internationalization:
|
||||
- Generate i18n declaration file: `npm run make-i18n`
|
||||
```
|
||||
|
||||
Now, please write a similar markdown for the current repository.
|
||||
Read all the GitHub workflows under .github/ of the repository (if this folder exists) to understand the CI checks (e.g., linter, pre-commit), and include those in the repo.md file.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: get_test_to_pass
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /fix_test
|
||||
inputs:
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
- name: TEST_COMMAND_TO_RUN
|
||||
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
|
||||
- name: FUNCTION_TO_FIX
|
||||
description: "The name of function to fix"
|
||||
- name: FILE_FOR_FUNCTION
|
||||
description: "The path of the file that contains the function"
|
||||
---
|
||||
|
||||
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
|
||||
|
||||
{%- if FUNCTION_TO_FIX and FILE_FOR_FUNCTION %}
|
||||
Help me fix these tests to pass by fixing the {{ FUNCTION_TO_FIX }} function in file {{ FILE_FOR_FUNCTION }}.
|
||||
{%- endif %}
|
||||
|
||||
PLEASE DO NOT modify the tests by yourselves -- Let me know if you think some of the tests are incorrect.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: pr_comments
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /pr_comments
|
||||
inputs:
|
||||
- name: PR_URL
|
||||
description: "URL of the pull request"
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch name corresponds to the pull request"
|
||||
---
|
||||
|
||||
First, check the branch {{ BRANCH_NAME }} and read the diff against the main branch to understand the purpose.
|
||||
|
||||
This branch corresponds to this PR {{ PR_URL }}
|
||||
|
||||
Next, you should use the GitHub API to read the reviews and comments on this PR and address them.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: pr_update
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /pr_update
|
||||
inputs:
|
||||
- name: PR_URL
|
||||
description: "URL of the pull request"
|
||||
type: string
|
||||
validation:
|
||||
pattern: "^https://github.com/.+/.+/pull/[0-9]+$"
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch name corresponds to the pull request"
|
||||
type: string
|
||||
---
|
||||
|
||||
Please check the branch "{{ BRANCH_NAME }}" and look at the diff against the main branch. This branch belongs to this PR "{{ PR_URL }}".
|
||||
|
||||
Once you understand the purpose of the diff, please use Github API to read the existing PR description, and update it to be more reflective of the changes we've made when necessary.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: update_test
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /update_test
|
||||
inputs:
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
- name: TEST_COMMAND_TO_RUN
|
||||
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
|
||||
---
|
||||
|
||||
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
|
||||
|
||||
{%- if FUNCTION_TO_FIX and FILE_FOR_FUNCTION %}
|
||||
Help me fix these tests to pass by fixing the {{ FUNCTION_TO_FIX }} function in file {{ FILE_FOR_FUNCTION }}.
|
||||
{%- endif %}
|
||||
|
||||
PLEASE DO NOT modify the tests by yourselves -- Let me know if you think some of the tests are incorrect.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import io
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
from typing import List, Union
|
||||
|
||||
import frontmatter
|
||||
from pydantic import BaseModel
|
||||
@@ -9,7 +10,7 @@ from openhands.core.exceptions import (
|
||||
MicroagentValidationError,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.microagent.types import MicroagentMetadata, MicroagentType
|
||||
from openhands.microagent.types import InputMetadata, MicroagentMetadata, MicroagentType
|
||||
|
||||
|
||||
class BaseMicroagent(BaseModel):
|
||||
@@ -71,13 +72,24 @@ class BaseMicroagent(BaseModel):
|
||||
subclass_map = {
|
||||
MicroagentType.KNOWLEDGE: KnowledgeMicroagent,
|
||||
MicroagentType.REPO_KNOWLEDGE: RepoMicroagent,
|
||||
MicroagentType.TASK: TaskMicroagent,
|
||||
}
|
||||
|
||||
# Infer the agent type:
|
||||
# 1. If triggers exist -> KNOWLEDGE
|
||||
# 2. Else (no triggers) -> REPO
|
||||
# 1. If inputs exist -> TASK
|
||||
# 2. If triggers exist -> KNOWLEDGE
|
||||
# 3. Else (no triggers) -> REPO
|
||||
inferred_type: MicroagentType
|
||||
if metadata.triggers:
|
||||
if metadata.inputs:
|
||||
inferred_type = MicroagentType.TASK
|
||||
# Add a trigger for the agent name if not already present
|
||||
trigger = f'/{metadata.name}'
|
||||
if not metadata.triggers or trigger not in metadata.triggers:
|
||||
if not metadata.triggers:
|
||||
metadata.triggers = [trigger]
|
||||
else:
|
||||
metadata.triggers.append(trigger)
|
||||
elif metadata.triggers:
|
||||
inferred_type = MicroagentType.KNOWLEDGE
|
||||
else:
|
||||
# No triggers, default to REPO unless metadata explicitly says otherwise (which it shouldn't for REPO)
|
||||
@@ -111,8 +123,8 @@ class KnowledgeMicroagent(BaseMicroagent):
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
if self.type != MicroagentType.KNOWLEDGE:
|
||||
raise ValueError('KnowledgeMicroagent must have type KNOWLEDGE')
|
||||
if self.type not in [MicroagentType.KNOWLEDGE, MicroagentType.TASK]:
|
||||
raise ValueError('KnowledgeMicroagent must have type KNOWLEDGE or TASK')
|
||||
|
||||
def match_trigger(self, message: str) -> str | None:
|
||||
"""Match a trigger in the message.
|
||||
@@ -150,6 +162,57 @@ class RepoMicroagent(BaseMicroagent):
|
||||
)
|
||||
|
||||
|
||||
class TaskMicroagent(KnowledgeMicroagent):
|
||||
"""TaskMicroagent is a special type of KnowledgeMicroagent that requires user input.
|
||||
|
||||
These microagents are triggered by a special format: "/{agent_name}"
|
||||
and will prompt the user for any required inputs before proceeding.
|
||||
"""
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
if self.type != MicroagentType.TASK:
|
||||
raise ValueError(
|
||||
f'TaskMicroagent initialized with incorrect type: {self.type}'
|
||||
)
|
||||
|
||||
# Append a prompt to ask for missing variables
|
||||
self._append_missing_variables_prompt()
|
||||
|
||||
def _append_missing_variables_prompt(self) -> None:
|
||||
"""Append a prompt to ask for missing variables."""
|
||||
# Check if the content contains any variables or has inputs defined
|
||||
if not self.requires_user_input() and not self.metadata.inputs:
|
||||
return
|
||||
|
||||
prompt = "\n\nIf the user didn't provide any of these variables, ask the user to provide them first before the agent can proceed with the task."
|
||||
self.content += prompt
|
||||
|
||||
def extract_variables(self, content: str) -> List[str]:
|
||||
"""Extract variables from the content.
|
||||
|
||||
Variables are in the format ${variable_name}.
|
||||
"""
|
||||
pattern = r'\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}'
|
||||
matches = re.findall(pattern, content)
|
||||
return matches
|
||||
|
||||
def requires_user_input(self) -> bool:
|
||||
"""Check if this microagent requires user input.
|
||||
|
||||
Returns True if the content contains variables in the format ${variable_name}.
|
||||
"""
|
||||
# Check if the content contains any variables
|
||||
variables = self.extract_variables(self.content)
|
||||
logger.debug(f'This microagent requires user input: {variables}')
|
||||
return len(variables) > 0
|
||||
|
||||
@property
|
||||
def inputs(self) -> List[InputMetadata]:
|
||||
"""Get the inputs for this microagent."""
|
||||
return self.metadata.inputs
|
||||
|
||||
|
||||
def load_microagents_from_dir(
|
||||
microagent_dir: Union[str, Path],
|
||||
) -> tuple[dict[str, RepoMicroagent], dict[str, KnowledgeMicroagent]]:
|
||||
@@ -161,7 +224,7 @@ def load_microagents_from_dir(
|
||||
microagent_dir: Path to the microagents directory (e.g. .openhands/microagents)
|
||||
|
||||
Returns:
|
||||
Tuple of (repo_agents, knowledge_agents, task_agents) dictionaries
|
||||
Tuple of (repo_agents, knowledge_agents) dictionaries
|
||||
"""
|
||||
if isinstance(microagent_dir, str):
|
||||
microagent_dir = Path(microagent_dir)
|
||||
@@ -182,6 +245,7 @@ def load_microagents_from_dir(
|
||||
if isinstance(agent, RepoMicroagent):
|
||||
repo_agents[agent.name] = agent
|
||||
elif isinstance(agent, KnowledgeMicroagent):
|
||||
# Both KnowledgeMicroagent and TaskMicroagent go into knowledge_agents
|
||||
knowledge_agents[agent.name] = agent
|
||||
logger.debug(f'Loaded agent {agent.name} from {file}')
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -8,6 +9,14 @@ class MicroagentType(str, Enum):
|
||||
|
||||
KNOWLEDGE = 'knowledge'
|
||||
REPO_KNOWLEDGE = 'repo'
|
||||
TASK = 'task' # Special type for task microagents that require user input
|
||||
|
||||
|
||||
class InputMetadata(BaseModel):
|
||||
"""Metadata for task microagent inputs."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
|
||||
|
||||
class MicroagentMetadata(BaseModel):
|
||||
@@ -18,3 +27,4 @@ class MicroagentMetadata(BaseModel):
|
||||
version: str = Field(default='1.0.0')
|
||||
agent: str = Field(default='CodeActAgent')
|
||||
triggers: list[str] = [] # optional, only exists for knowledge microagents
|
||||
inputs: List[InputMetadata] = [] # optional, only exists for task microagents
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import Any
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from openhands.security.options import SecurityAnalyzers
|
||||
from fastapi import APIRouter, Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.microagent.microagent import KnowledgeMicroagent, TaskMicroagent
|
||||
from openhands.security.options import SecurityAnalyzers
|
||||
from openhands.server.shared import config, server_config
|
||||
from openhands.utils.llm import get_supported_llm_models
|
||||
|
||||
@@ -67,3 +68,60 @@ async def get_config() -> dict[str, Any]:
|
||||
dict[str, Any]: The current server configuration.
|
||||
"""
|
||||
return server_config.get_config()
|
||||
|
||||
|
||||
class MicroagentInfo(BaseModel):
|
||||
name: str
|
||||
trigger: str
|
||||
description: str
|
||||
|
||||
|
||||
@app.get('/microagents', response_model=List[Dict[str, str]])
|
||||
async def get_microagents(
|
||||
request: Request,
|
||||
) -> List[Dict[str, str]]:
|
||||
"""Get all available microagents for the current session.
|
||||
|
||||
To get the microagents:
|
||||
```sh
|
||||
curl http://localhost:3000/api/options/microagents
|
||||
```
|
||||
|
||||
Returns:
|
||||
List[Dict[str, str]]: A list of microagent information including name and trigger.
|
||||
"""
|
||||
# Check if we have a conversation in the request state
|
||||
if not hasattr(request.state, 'conversation') or not request.state.conversation:
|
||||
return []
|
||||
|
||||
# Get the runtime from the conversation
|
||||
if (
|
||||
not hasattr(request.state.conversation, 'runtime')
|
||||
or not request.state.conversation.runtime
|
||||
):
|
||||
return []
|
||||
|
||||
# Get the agent session from the runtime
|
||||
runtime = request.state.conversation.runtime
|
||||
if not hasattr(runtime, 'agent_session') or not runtime.agent_session:
|
||||
return []
|
||||
|
||||
# Get the memory from the agent session
|
||||
agent_session = runtime.agent_session
|
||||
if not hasattr(agent_session, 'memory') or not agent_session.memory:
|
||||
return []
|
||||
|
||||
# Get all knowledge microagents from memory
|
||||
microagents = []
|
||||
for agent in agent_session.memory.knowledge_microagents.values():
|
||||
if isinstance(agent, (KnowledgeMicroagent, TaskMicroagent)) and agent.triggers:
|
||||
# Use the first trigger as the main one
|
||||
trigger = agent.triggers[0]
|
||||
# Extract a short description from the content (first line or paragraph)
|
||||
description = agent.content.strip().split('\n')[0][:100]
|
||||
|
||||
microagents.append(
|
||||
{'name': agent.name, 'trigger': trigger, 'description': description}
|
||||
)
|
||||
|
||||
return microagents
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for microagent loading in runtime."""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from conftest import (
|
||||
@@ -7,7 +8,13 @@ from conftest import (
|
||||
_load_runtime,
|
||||
)
|
||||
|
||||
from openhands.microagent import KnowledgeMicroagent, RepoMicroagent
|
||||
from openhands.microagent.microagent import (
|
||||
BaseMicroagent,
|
||||
KnowledgeMicroagent,
|
||||
RepoMicroagent,
|
||||
TaskMicroagent,
|
||||
)
|
||||
from openhands.microagent.types import MicroagentType
|
||||
|
||||
|
||||
def _create_test_microagents(test_dir: str):
|
||||
@@ -165,3 +172,173 @@ Repository-specific test instructions.
|
||||
|
||||
finally:
|
||||
_close_test_runtime(runtime)
|
||||
|
||||
|
||||
def test_task_microagent_creation():
|
||||
"""Test that a TaskMicroagent is created correctly."""
|
||||
content = """---
|
||||
name: test_task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /test_task
|
||||
inputs:
|
||||
- name: TEST_VAR
|
||||
description: "Test variable"
|
||||
---
|
||||
|
||||
This is a test task microagent with a variable: ${test_var}.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.md') as f:
|
||||
f.write(content.encode())
|
||||
f.flush()
|
||||
|
||||
agent = BaseMicroagent.load(f.name)
|
||||
|
||||
assert isinstance(agent, TaskMicroagent)
|
||||
assert agent.type == MicroagentType.TASK
|
||||
assert agent.name == 'test_task'
|
||||
assert '/test_task' in agent.triggers
|
||||
assert "If the user didn't provide any of these variables" in agent.content
|
||||
|
||||
|
||||
def test_task_microagent_variable_extraction():
|
||||
"""Test that variables are correctly extracted from the content."""
|
||||
content = """---
|
||||
name: test_task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /test_task
|
||||
inputs:
|
||||
- name: var1
|
||||
description: "Variable 1"
|
||||
---
|
||||
|
||||
This is a test with variables: ${var1}, ${var2}, and ${var3}.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.md') as f:
|
||||
f.write(content.encode())
|
||||
f.flush()
|
||||
|
||||
agent = BaseMicroagent.load(f.name)
|
||||
|
||||
assert isinstance(agent, TaskMicroagent)
|
||||
variables = agent.extract_variables(agent.content)
|
||||
assert set(variables) == {'var1', 'var2', 'var3'}
|
||||
assert agent.requires_user_input()
|
||||
|
||||
|
||||
def test_knowledge_microagent_no_prompt():
|
||||
"""Test that a regular KnowledgeMicroagent doesn't get the prompt."""
|
||||
content = """---
|
||||
name: test_knowledge
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- test_knowledge
|
||||
---
|
||||
|
||||
This is a test knowledge microagent.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.md') as f:
|
||||
f.write(content.encode())
|
||||
f.flush()
|
||||
|
||||
agent = BaseMicroagent.load(f.name)
|
||||
|
||||
assert isinstance(agent, KnowledgeMicroagent)
|
||||
assert agent.type == MicroagentType.KNOWLEDGE
|
||||
assert "If the user didn't provide any of these variables" not in agent.content
|
||||
|
||||
|
||||
def test_task_microagent_trigger_addition():
|
||||
"""Test that a trigger is added if not present."""
|
||||
content = """---
|
||||
name: test_task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
inputs:
|
||||
- name: TEST_VAR
|
||||
description: "Test variable"
|
||||
---
|
||||
|
||||
This is a test task microagent.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.md') as f:
|
||||
f.write(content.encode())
|
||||
f.flush()
|
||||
|
||||
agent = BaseMicroagent.load(f.name)
|
||||
|
||||
assert isinstance(agent, TaskMicroagent)
|
||||
assert '/test_task' in agent.triggers
|
||||
|
||||
|
||||
def test_task_microagent_no_duplicate_trigger():
|
||||
"""Test that a trigger is not duplicated if already present."""
|
||||
content = """---
|
||||
name: test_task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /test_task
|
||||
- another_trigger
|
||||
inputs:
|
||||
- name: TEST_VAR
|
||||
description: "Test variable"
|
||||
---
|
||||
|
||||
This is a test task microagent.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.md') as f:
|
||||
f.write(content.encode())
|
||||
f.flush()
|
||||
|
||||
agent = BaseMicroagent.load(f.name)
|
||||
|
||||
assert isinstance(agent, TaskMicroagent)
|
||||
assert agent.triggers.count('/test_task') == 1 # No duplicates
|
||||
assert len(agent.triggers) == 2
|
||||
assert 'another_trigger' in agent.triggers
|
||||
assert '/test_task' in agent.triggers
|
||||
|
||||
|
||||
def test_task_microagent_match_trigger():
|
||||
"""Test that a task microagent matches its trigger correctly."""
|
||||
content = """---
|
||||
name: test_task
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /test_task
|
||||
inputs:
|
||||
- name: TEST_VAR
|
||||
description: "Test variable"
|
||||
---
|
||||
|
||||
This is a test task microagent.
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.md') as f:
|
||||
f.write(content.encode())
|
||||
f.flush()
|
||||
|
||||
agent = BaseMicroagent.load(f.name)
|
||||
|
||||
assert isinstance(agent, TaskMicroagent)
|
||||
assert agent.match_trigger('/test_task') == '/test_task'
|
||||
assert agent.match_trigger(' /test_task ') == '/test_task'
|
||||
assert agent.match_trigger('This contains /test_task') == '/test_task'
|
||||
assert agent.match_trigger('/other_task') is None
|
||||
|
||||
Reference in New Issue
Block a user