fix(ai): keep assistant actions visible (#236)
This commit is contained in:
1 parent
03a9444c92
commit
db58da7c3e
2 files changed
+39
-6
No files matched your search
@@ -401,7 +401,7 @@ export const AiAssistantDialog = ({
|
||||
<section
|
||||
ref={assignPanelRef}
|
||||
aria-label={t("aiAssistant.title")}
|
||||
className="fixed z-[70] max-h-[70dvh] w-[min(36rem,calc(100vw-1.5rem))] overflow-y-auto rounded-xl border border-slate-200 bg-white p-4 shadow-2xl ring-1 ring-slate-950/5"
|
||||
className="fixed z-[70] flex max-h-[70dvh] w-[min(36rem,calc(100vw-1.5rem))] flex-col overflow-hidden rounded-xl border border-slate-200 bg-white p-4 shadow-2xl ring-1 ring-slate-950/5"
|
||||
role="dialog"
|
||||
style={panelStyle}
|
||||
onKeyDown={(event) => {
|
||||
@@ -412,7 +412,7 @@ export const AiAssistantDialog = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div className="mb-3 flex shrink-0 items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 shrink-0 text-emerald-600" />
|
||||
<span className="truncate text-sm font-semibold text-slate-950">{t("aiAssistant.title")}</span>
|
||||
@@ -424,7 +424,8 @@ export const AiAssistantDialog = ({
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-1">
|
||||
<div className="grid gap-4">
|
||||
{hasSelection ? (
|
||||
<p className="max-h-12 overflow-hidden whitespace-pre-wrap border-l-2 border-emerald-200 pl-3 text-xs leading-5 text-slate-500">
|
||||
{selectionMarkdown}
|
||||
@@ -474,7 +475,7 @@ export const AiAssistantDialog = ({
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="grid min-w-0 grid-cols-2 gap-2 sm:grid-cols-[auto_auto]">
|
||||
<div className="grid min-w-0 grid-cols-2 gap-2 sm:grid-cols-[8rem_7rem]">
|
||||
<Button
|
||||
type="button"
|
||||
variant={!selectedPromptId && action === "custom" ? "solid" : "outline"}
|
||||
@@ -671,9 +672,10 @@ export const AiAssistantDialog = ({
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{output ? (
|
||||
<div className="mt-4 flex flex-wrap justify-between gap-2">
|
||||
<div className="mt-3 flex shrink-0 flex-wrap justify-between gap-2 border-t border-slate-200 pt-3" data-ai-assistant-actions>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button type="button" variant="outline" disabled={isGenerating} onClick={() => void copy()}>{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}{t(copied ? "aiAssistant.copied" : "aiAssistant.copy")}</Button>
|
||||
<Button type="button" variant="outline" disabled={isGenerating} onClick={() => { setOutput(""); setError(null); }}><Trash2 className="h-4 w-4" />{t("aiAssistant.discard")}</Button>
|
||||
|
||||
@@ -182,7 +182,21 @@ test.describe("AI custom prompts", () => {
|
||||
const assistant = page.getByRole("dialog", { name: "AI 笔记助手" });
|
||||
await expect(assistant).toBeVisible();
|
||||
const customInstructionButton = assistant.getByRole("button", { name: "自定义指令", exact: true });
|
||||
const actionSelect = assistant.getByRole("combobox", { name: "处理方式" });
|
||||
const generateButton = assistant.getByRole("button", { name: "生成", exact: true });
|
||||
await expect(customInstructionButton).toBeVisible();
|
||||
const [actionSelectBox, customInstructionBox, generateBox] = await Promise.all([
|
||||
actionSelect.boundingBox(),
|
||||
customInstructionButton.boundingBox(),
|
||||
generateButton.boundingBox(),
|
||||
]);
|
||||
expect(actionSelectBox).not.toBeNull();
|
||||
expect(customInstructionBox).not.toBeNull();
|
||||
expect(generateBox).not.toBeNull();
|
||||
expect(customInstructionBox!.width).toBeGreaterThanOrEqual(120);
|
||||
expect(generateBox!.width).toBeGreaterThanOrEqual(104);
|
||||
expect(actionSelectBox!.width / customInstructionBox!.width).toBeLessThan(2.5);
|
||||
expect(customInstructionBox!.width / generateBox!.width).toBeLessThan(1.25);
|
||||
const customInstructionLineCount = await customInstructionButton.evaluate((element) => {
|
||||
const textNode = Array.from(element.childNodes)
|
||||
.find((node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim());
|
||||
@@ -409,6 +423,7 @@ test.describe("AI custom prompts", () => {
|
||||
});
|
||||
|
||||
test("inserts generated content at the caret captured when AI opens", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1180, height: 720 });
|
||||
const memo = await createMemo(
|
||||
page,
|
||||
`e2e-ai-caret-insert-${Date.now()}`,
|
||||
@@ -428,7 +443,23 @@ test.describe("AI custom prompts", () => {
|
||||
const dialog = page.getByRole("dialog", { name: "AI 笔记助手" });
|
||||
await dialog.getByRole("button", { name: "生成", exact: true }).click();
|
||||
await expect(dialog.getByText("AI 插入段落", { exact: true })).toBeVisible();
|
||||
await dialog.getByRole("button", { name: "追加到笔记", exact: true }).click();
|
||||
const copyButton = dialog.getByRole("button", { name: "复制结果", exact: true });
|
||||
const appendButton = dialog.getByRole("button", { name: "追加到笔记", exact: true });
|
||||
await expect(copyButton).toBeVisible();
|
||||
await expect(appendButton).toBeVisible();
|
||||
const [dialogBox, copyButtonBox, appendButtonBox] = await Promise.all([
|
||||
dialog.boundingBox(),
|
||||
copyButton.boundingBox(),
|
||||
appendButton.boundingBox(),
|
||||
]);
|
||||
expect(dialogBox).not.toBeNull();
|
||||
expect(copyButtonBox).not.toBeNull();
|
||||
expect(appendButtonBox).not.toBeNull();
|
||||
expect(copyButtonBox!.y).toBeGreaterThanOrEqual(dialogBox!.y);
|
||||
expect(copyButtonBox!.y + copyButtonBox!.height).toBeLessThanOrEqual(dialogBox!.y + dialogBox!.height);
|
||||
expect(appendButtonBox!.y).toBeGreaterThanOrEqual(dialogBox!.y);
|
||||
expect(appendButtonBox!.y + appendButtonBox!.height).toBeLessThanOrEqual(dialogBox!.y + dialogBox!.height);
|
||||
await appendButton.click();
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect.poll(() => editor.locator(":scope > p").allTextContents()).toEqual([
|
||||
"第一段",
|
||||
|
||||
Reference in new issue
Block a user