feat: add svelte workspace feature panels

This commit is contained in:
2026-07-18 16:42:05 +08:00
parent a758996799
commit 7a2af1b938
6 changed files with 142 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
<script lang="ts">
export type AISessionCard = {
id: number;
title: string;
updatedAt: string;
};
export let sessions: AISessionCard[] = [];
</script>
<section aria-label="AI 会话" class="ai-session-grid">
{#each sessions as session}
<button class="ai-session-card" type="button">
<strong>{session.title}</strong>
<span>{session.updatedAt}</span>
</button>
{/each}
</section>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
export let onCapture: (input: { sourceType: string; body: string }) => void = () => {};
let body = '';
let sourceType = 'text';
function submit() {
onCapture({ sourceType, body });
body = '';
}
</script>
<section aria-label="项目 Inbox" class="inbox-panel">
<select aria-label="收集类型" bind:value={sourceType}>
<option value="text">文本</option>
<option value="link">链接</option>
<option value="file">文件</option>
</select>
<textarea aria-label="收集内容" bind:value={body}></textarea>
<div class="toolbar">
<button type="button" on:click={submit}>保存到 Inbox</button>
<button type="button">分析/整理</button>
</div>
</section>

View File

@@ -0,0 +1,41 @@
<script lang="ts">
export type Suggestion = {
kind: 'task' | 'note' | 'source';
title: string;
body: string;
};
export let suggestions: Suggestion[] = [];
export let onConfirm: (selected: Suggestion[]) => void;
let selectedIndexes: number[] = [];
function toggle(index: number) {
selectedIndexes = selectedIndexes.includes(index)
? selectedIndexes.filter((item) => item !== index)
: [...selectedIndexes, index];
}
function confirm() {
onConfirm(selectedIndexes.map((index) => suggestions[index]));
}
</script>
<section aria-label="AI 整理建议" class="suggestions">
{#each suggestions as suggestion, index}
<label class="suggestion">
<input
aria-label={`选择 ${suggestion.title}`}
type="checkbox"
checked={selectedIndexes.includes(index)}
on:change={() => toggle(index)}
/>
<span class="suggestion-body">
<strong>{suggestion.title}</strong>
<small>{suggestion.kind}</small>
<span>{suggestion.body}</span>
</span>
</label>
{/each}
<button type="button" on:click={confirm}>创建选中项</button>
</section>

View File

@@ -0,0 +1,25 @@
import '@testing-library/jest-dom/vitest';
import { fireEvent, render, screen } from '@testing-library/svelte';
import { describe, expect, it, vi } from 'vitest';
import SuggestionList from './SuggestionList.svelte';
describe('SuggestionList', () => {
it('requires explicit selection before confirmation', async () => {
const onConfirm = vi.fn();
render(SuggestionList, {
props: {
suggestions: [{ kind: 'task', title: '跟进报价', body: '联系客户' }],
onConfirm,
},
});
await fireEvent.click(screen.getByText('创建选中项'));
expect(onConfirm).toHaveBeenCalledWith([]);
await fireEvent.click(screen.getByLabelText('选择 跟进报价'));
await fireEvent.click(screen.getByText('创建选中项'));
expect(onConfirm).toHaveBeenLastCalledWith([
{ kind: 'task', title: '跟进报价', body: '联系客户' },
]);
});
});

View File

@@ -0,0 +1,12 @@
<script lang="ts">
export let value = '';
export let onChange: (value: string) => void = () => {};
</script>
<textarea
aria-label="Markdown 编辑器"
class="markdown-editor"
spellcheck="false"
bind:value
on:input={() => onChange(value)}
></textarea>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
export type TaskListItem = {
id: number;
title: string;
status: 'open' | 'doing' | 'done';
assignee?: string;
};
export let tasks: TaskListItem[] = [];
</script>
<section aria-label="任务列表" class="task-list">
{#each tasks as task}
<article class="task-row">
<strong>{task.title}</strong>
<span>{task.status}</span>
{#if task.assignee}
<span>{task.assignee}</span>
{/if}
</article>
{/each}
</section>