@promptbook/cli 0.112.0-125 → 0.112.0-127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/apps/agents-server/README.md +3 -0
- package/apps/agents-server/playwright.config.ts +1 -0
- package/apps/agents-server/public/promptbook-logo-blue.png +0 -0
- package/apps/agents-server/public/promptbook-logo-white.png +0 -0
- package/apps/agents-server/src/app/[agentName]/layout.tsx +1 -0
- package/apps/agents-server/src/app/actions.ts +2 -2
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +68 -36
- package/apps/agents-server/src/app/agents/[agentName]/ActiveAgentBreadcrumbBinder.tsx +30 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +2 -0
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatHistoryPayloadState.ts +38 -4
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatHistorySyncOperations.ts +4 -1
- package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +19 -15
- package/apps/agents-server/src/app/agents/[agentName]/chat/useCanonicalAgentChatPanelState.ts +58 -7
- package/apps/agents-server/src/app/agents/[agentName]/layout.tsx +47 -1
- package/apps/agents-server/src/app/api/chat/citation-label/route.ts +133 -0
- package/apps/agents-server/src/app/globals.css +78 -0
- package/apps/agents-server/src/components/Header/ActiveAgentBreadcrumbContext.tsx +82 -0
- package/apps/agents-server/src/components/Header/useHeaderActiveAgent.ts +26 -7
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +35 -32
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/ManGoNewAgentWizard.tsx +151 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/ManGoOnboardingNavigation.tsx +50 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/BookLanguagePanel.tsx +125 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/DropZone.tsx +83 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/EmailTestRun.tsx +216 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/KnowledgeList.tsx +79 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/Logo.tsx +20 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/MarkdownBookEditor.tsx +154 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/MarkdownPreview.tsx +188 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/RailStepper.tsx +115 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/StepFrame.tsx +47 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/TestChat.tsx +159 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/WizardShell.tsx +114 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/brand/RailArtwork.tsx +60 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/BookStep.tsx +135 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/DoneStep.tsx +215 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/KnowledgeStep.tsx +152 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/TestStep.tsx +262 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/ZadaniStep.tsx +64 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Badge.tsx +41 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Banner.tsx +69 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Button.tsx +77 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Card.tsx +33 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Field.tsx +103 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/IconButton.tsx +40 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Spinner.tsx +14 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/tokens.ts +37 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/bookSections.ts +62 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/emailScenarios.ts +22 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/steps.ts +46 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/index.ts +11 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/cn.ts +4 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/format.ts +9 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/id.ts +11 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/agentEvalService.ts +30 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/agentTestService.ts +69 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/bookService.ts +21 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/createManGoAgentSource.ts +107 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/draftService.ts +45 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/uploadService.ts +27 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/state/OnboardingProvider.tsx +121 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/types.ts +52 -0
- package/apps/agents-server/src/components/NewAgentDialog/createNewAgentWizardSource.ts +1 -1
- package/apps/agents-server/src/components/NewAgentDialog/trackNewAgentCreationEvent.ts +1 -1
- package/apps/agents-server/src/components/NewAgentDialog/useNewAgentDialog.tsx +76 -0
- package/apps/agents-server/src/constants/newAgentWizard.ts +17 -8
- package/apps/agents-server/src/database/getMetadata.ts +37 -1
- package/apps/agents-server/src/database/metadataDefaults.ts +18 -7
- package/apps/agents-server/src/database/migrations/2026-06-2200-new-agent-wizard-metadata-key.sql +27 -0
- package/apps/agents-server/src/database/migrations/2026-06-2201-new-agent-wizard-mango-default.sql +11 -0
- package/apps/agents-server/src/generated/reservedPaths.ts +2 -0
- package/apps/agents-server/src/languages/translations/english.yaml +1 -1
- package/apps/agents-server/src/tools/createChatAttachmentToolFunctions.ts +8 -4
- package/apps/agents-server/src/utils/chat/resolveAgentsServerCitationLabel.ts +130 -0
- package/apps/agents-server/src/utils/chat/resolveCitationSourceLabel.ts +436 -0
- package/apps/agents-server/src/utils/knowledge/resolveWebsiteKnowledgeSourcesForServer.ts +10 -1
- package/apps/agents-server/src/utils/session.ts +62 -5
- package/apps/agents-server/src/utils/transpilers/resolveTranspiledTeamExport.ts +10 -10
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +198 -63
- package/apps/agents-server/src/utils/vpsConfiguration.ts +1 -0
- package/apps/agents-server/src/utils/vpsSelfUpdate.ts +81 -8
- package/esm/index.es.js +1052 -455
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
- package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -2
- package/esm/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +50 -0
- package/esm/scripts/run-codex-prompts/server/buildCoderServerRunState.d.ts +23 -0
- package/esm/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
- package/esm/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +13 -1
- package/esm/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
- package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +1 -0
- package/esm/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +4 -0
- package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +1 -0
- package/esm/src/_packages/components.index.d.ts +2 -0
- package/esm/src/_packages/types.index.d.ts +2 -0
- package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/_packages/components.index.ts +2 -0
- package/src/_packages/types.index.ts +2 -0
- package/src/avatars/avatarAnimationScheduler.ts +61 -20
- package/src/avatars/visuals/octopus3d2AvatarVisual.ts +106 -21
- package/src/avatars/visuals/octopus3d3AvatarVisual.ts +131 -28
- package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments/augmentAgentModelRequirementsFromSource.ts +18 -2
- package/src/book-2.0/book-language-documentation/renderGroupedCommitmentDocumentationMarkdown.ts +7 -1
- package/src/book-components/Chat/Chat/Chat.tsx +4 -0
- package/src/book-components/Chat/Chat/ChatCitationModal.tsx +17 -3
- package/src/book-components/Chat/Chat/ChatMessageItem.tsx +22 -2
- package/src/book-components/Chat/Chat/ChatMessageList.tsx +6 -0
- package/src/book-components/Chat/Chat/ChatProps.tsx +6 -0
- package/src/book-components/Chat/Chat/ChatToolCallModal.tsx +4 -0
- package/src/book-components/Chat/Chat/ChatToolCallModalContent.tsx +4 -0
- package/src/book-components/Chat/Chat/TeamToolCallModalContent.tsx +16 -3
- package/src/book-components/Chat/Chat/useChatPostprocessedMessages.ts +13 -2
- package/src/book-components/Chat/SourceChip/SourceChip.module.css +8 -0
- package/src/book-components/Chat/SourceChip/SourceChip.tsx +19 -5
- package/src/book-components/Chat/hooks/useResolvedCitationLabel.ts +65 -0
- package/src/book-components/Chat/save/html/htmlSaveFormatDefinition.ts +315 -88
- package/src/book-components/Chat/save/react/reactSaveFormatDefinition.ts +7 -1
- package/src/book-components/Chat/types/ChatMessage.ts +5 -0
- package/src/book-components/Chat/types/ChatParticipant.ts +1 -1
- package/src/book-components/Chat/types/CitationLabelResolver.ts +9 -0
- package/src/book-components/Chat/utils/citationHelpers.ts +90 -2
- package/src/book-components/Chat/utils/createCitationFootnoteRenderModel.ts +3 -0
- package/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.ts +178 -0
- package/src/book-components/Chat/utils/parseCitationsFromContent.ts +6 -0
- package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +13 -2
- package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +1 -1
- package/src/cli/cli-commands/coder/run.ts +17 -20
- package/src/cli/cli-commands/coder/server.ts +13 -16
- package/src/cli/common/$deprecateCliCommand.ts +10 -1
- package/src/commitments/GOAL/GOAL.ts +7 -2
- package/src/commitments/LANGUAGE/LANGUAGE.ts +7 -2
- package/src/commitments/TEAM/TEAM.ts +12 -1
- package/src/commitments/USE_PROJECT/createUseProjectToolFunctions.ts +8 -1
- package/src/import-plugins/JsonFileImportPlugin.ts +15 -2
- package/src/import-plugins/TextFileImportPlugin.ts +8 -1
- package/src/llm-providers/_common/register/$registeredLlmToolsMessage.ts +6 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +696 -941
- package/src/utils/chat/chatAttachments/resolveChatAttachmentContent.ts +8 -1
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -1
- package/umd/index.umd.js +1055 -458
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
- package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -2
- package/umd/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +50 -0
- package/umd/scripts/run-codex-prompts/server/buildCoderServerRunState.d.ts +23 -0
- package/umd/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
- package/umd/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +13 -1
- package/umd/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
- package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +1 -0
- package/umd/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +4 -0
- package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +1 -0
- package/umd/src/_packages/components.index.d.ts +2 -0
- package/umd/src/_packages/types.index.d.ts +2 -0
- package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/umd/src/version.d.ts +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { BOOK_SECTION_PRESETS } from '../config/bookSections';
|
|
6
|
+
import { cn } from '../lib/cn';
|
|
7
|
+
import { MarkdownPreview } from './MarkdownPreview';
|
|
8
|
+
import { Badge } from './ui/Badge';
|
|
9
|
+
import { Spinner } from './ui/Spinner';
|
|
10
|
+
|
|
11
|
+
type MarkdownBookEditorProps = {
|
|
12
|
+
readonly value: string;
|
|
13
|
+
readonly onChange: (value: string) => void;
|
|
14
|
+
readonly onRegenerate?: () => void;
|
|
15
|
+
readonly isRegenerating?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ViewMode = 'edit' | 'split' | 'preview';
|
|
19
|
+
|
|
20
|
+
const VIEW_MODES: ReadonlyArray<{ id: ViewMode; label: string; icon: string }> = [
|
|
21
|
+
{ id: 'edit', label: 'Editor', icon: '✎' },
|
|
22
|
+
{ id: 'split', label: 'Obojí', icon: '⊟' },
|
|
23
|
+
{ id: 'preview', label: 'Náhled', icon: '◳' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const PANE_HEIGHT = 'h-[28rem]';
|
|
27
|
+
|
|
28
|
+
export function MarkdownBookEditor({ value, onChange, onRegenerate, isRegenerating }: MarkdownBookEditorProps) {
|
|
29
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
30
|
+
const [mode, setMode] = useState<ViewMode>('edit');
|
|
31
|
+
|
|
32
|
+
function appendSection(markdown: string) {
|
|
33
|
+
const trimmed = value.replace(/\s+$/, '');
|
|
34
|
+
const next = trimmed ? `${trimmed}\n\n${markdown}\n` : `${markdown}\n`;
|
|
35
|
+
onChange(next);
|
|
36
|
+
|
|
37
|
+
// Make sure the new section is visible to edit.
|
|
38
|
+
if (mode === 'preview') {
|
|
39
|
+
setMode('split');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const textarea = textareaRef.current;
|
|
43
|
+
if (textarea) {
|
|
44
|
+
textarea.focus();
|
|
45
|
+
requestAnimationFrame(() => {
|
|
46
|
+
textarea.scrollTop = textarea.scrollHeight;
|
|
47
|
+
textarea.setSelectionRange(next.length, next.length);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isEditorShown = mode !== 'preview';
|
|
53
|
+
const isPreviewShown = mode !== 'edit';
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div>
|
|
57
|
+
<div className="overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-[var(--ob-shadow-md)] transition-shadow focus-within:shadow-[var(--ob-shadow-lg)] focus-within:ring-1 focus-within:ring-[color:var(--ob-accent-200)]">
|
|
58
|
+
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-zinc-200 bg-zinc-50/80 px-4 py-2.5">
|
|
59
|
+
<span className="flex items-center gap-2 text-[13px] text-zinc-600">
|
|
60
|
+
<span aria-hidden>📄</span>
|
|
61
|
+
<strong className="font-semibold text-zinc-800">book.md</strong>
|
|
62
|
+
<span className="hidden sm:inline">
|
|
63
|
+
<Badge tone="accent">Plně editovatelný</Badge>
|
|
64
|
+
</span>
|
|
65
|
+
</span>
|
|
66
|
+
<span className="flex items-center gap-2">
|
|
67
|
+
<ViewToggle mode={mode} onChange={setMode} />
|
|
68
|
+
{onRegenerate && (
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
onClick={onRegenerate}
|
|
72
|
+
disabled={isRegenerating}
|
|
73
|
+
title="Přegenerovat ze zadání"
|
|
74
|
+
className="inline-flex items-center gap-1.5 rounded-lg px-1.5 py-1 text-xs font-medium text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-[color:var(--ob-accent-700)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ob-ring)] disabled:cursor-not-allowed disabled:opacity-50"
|
|
75
|
+
>
|
|
76
|
+
{isRegenerating ? <Spinner className="h-3.5 w-3.5" /> : <span aria-hidden>↻</span>}
|
|
77
|
+
<span className="hidden md:inline">Přegenerovat</span>
|
|
78
|
+
</button>
|
|
79
|
+
)}
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div className={cn('grid', mode === 'split' && 'md:grid-cols-2 md:divide-x md:divide-zinc-200')}>
|
|
84
|
+
{isEditorShown && (
|
|
85
|
+
<textarea
|
|
86
|
+
ref={textareaRef}
|
|
87
|
+
value={value}
|
|
88
|
+
spellCheck={false}
|
|
89
|
+
onChange={(event) => onChange(event.target.value)}
|
|
90
|
+
aria-label="Obsah booku v markdownu"
|
|
91
|
+
className={cn(
|
|
92
|
+
'block w-full resize-none border-0 bg-white p-4 font-mono text-[13px] leading-relaxed text-zinc-800 focus:outline-none focus:ring-0',
|
|
93
|
+
mode === 'edit' ? 'min-h-[28rem] resize-y' : PANE_HEIGHT,
|
|
94
|
+
)}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
{isPreviewShown && (
|
|
98
|
+
<div className={cn('overflow-y-auto bg-white p-4', mode === 'preview' ? 'min-h-[28rem]' : PANE_HEIGHT)}>
|
|
99
|
+
{value.trim() ? (
|
|
100
|
+
<MarkdownPreview source={value} />
|
|
101
|
+
) : (
|
|
102
|
+
<p className="text-sm text-zinc-400">Náhled se zobrazí, jakmile book nebude prázdný.</p>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div className="mt-4">
|
|
110
|
+
<div className="mb-2 text-xs font-semibold text-zinc-500">Přidat předepsanou sekci:</div>
|
|
111
|
+
<div className="flex flex-wrap gap-2">
|
|
112
|
+
{BOOK_SECTION_PRESETS.map((preset) => (
|
|
113
|
+
<button
|
|
114
|
+
key={preset.key}
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => appendSection(preset.markdown)}
|
|
117
|
+
className="inline-flex items-center gap-1 rounded-full border border-dashed border-zinc-300 bg-white px-3 py-1.5 text-xs font-medium text-zinc-500 transition-colors hover:border-[color:var(--ob-accent-400)] hover:bg-[color:var(--ob-accent-50)] hover:text-[color:var(--ob-accent-700)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ob-ring)]"
|
|
118
|
+
>
|
|
119
|
+
+ {preset.label}
|
|
120
|
+
</button>
|
|
121
|
+
))}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function ViewToggle({ mode, onChange }: { readonly mode: ViewMode; readonly onChange: (mode: ViewMode) => void }) {
|
|
129
|
+
return (
|
|
130
|
+
<div className="inline-flex rounded-lg border border-zinc-200 bg-white p-0.5" role="tablist" aria-label="Zobrazení editoru">
|
|
131
|
+
{VIEW_MODES.map((view) => {
|
|
132
|
+
const isActive = view.id === mode;
|
|
133
|
+
return (
|
|
134
|
+
<button
|
|
135
|
+
key={view.id}
|
|
136
|
+
type="button"
|
|
137
|
+
role="tab"
|
|
138
|
+
aria-selected={isActive}
|
|
139
|
+
onClick={() => onChange(view.id)}
|
|
140
|
+
className={cn(
|
|
141
|
+
'inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ob-ring)]',
|
|
142
|
+
isActive
|
|
143
|
+
? 'bg-[color:var(--ob-accent-600)] text-white'
|
|
144
|
+
: 'text-zinc-500 hover:bg-zinc-100 hover:text-zinc-800',
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<span aria-hidden>{view.icon}</span>
|
|
148
|
+
<span className="hidden sm:inline">{view.label}</span>
|
|
149
|
+
</button>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/cn';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tiny, dependency-free Markdown renderer for the book preview. Renders to React nodes
|
|
7
|
+
* (no `dangerouslySetInnerHTML`), covering the subset used in agent books: headings,
|
|
8
|
+
* bold/italic/inline-code/links, unordered & ordered lists, blockquotes, horizontal rules
|
|
9
|
+
* and paragraphs. Anything it doesn't recognize falls through as plain paragraph text.
|
|
10
|
+
*/
|
|
11
|
+
export function MarkdownPreview({ source, className }: { readonly source: string; readonly className?: string }) {
|
|
12
|
+
return <div className={cn('text-sm leading-relaxed text-zinc-700', className)}>{renderBlocks(source)}</div>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderBlocks(source: string): ReactNode[] {
|
|
16
|
+
const lines = source.replace(/\r\n/g, '\n').split('\n');
|
|
17
|
+
const blocks: ReactNode[] = [];
|
|
18
|
+
let index = 0;
|
|
19
|
+
let key = 0;
|
|
20
|
+
|
|
21
|
+
while (index < lines.length) {
|
|
22
|
+
const line = lines[index];
|
|
23
|
+
|
|
24
|
+
if (line.trim() === '') {
|
|
25
|
+
index += 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const heading = /^(#{1,4})\s+(.*)$/.exec(line);
|
|
30
|
+
if (heading) {
|
|
31
|
+
blocks.push(renderHeading(heading[1].length, heading[2], key++));
|
|
32
|
+
index += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
|
|
37
|
+
blocks.push(<hr key={key++} className="my-4 border-zinc-200" />);
|
|
38
|
+
index += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (line.startsWith('>')) {
|
|
43
|
+
const quote: string[] = [];
|
|
44
|
+
while (index < lines.length && lines[index].startsWith('>')) {
|
|
45
|
+
quote.push(lines[index].replace(/^>\s?/, ''));
|
|
46
|
+
index += 1;
|
|
47
|
+
}
|
|
48
|
+
blocks.push(
|
|
49
|
+
<blockquote key={key++} className="my-3 border-l-2 border-[color:var(--ob-accent-300)] pl-3 italic text-zinc-500">
|
|
50
|
+
{renderInline(quote.join(' '))}
|
|
51
|
+
</blockquote>,
|
|
52
|
+
);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (/^[-*+]\s+/.test(line)) {
|
|
57
|
+
const items: string[] = [];
|
|
58
|
+
while (index < lines.length && /^[-*+]\s+/.test(lines[index])) {
|
|
59
|
+
items.push(lines[index].replace(/^[-*+]\s+/, ''));
|
|
60
|
+
index += 1;
|
|
61
|
+
}
|
|
62
|
+
blocks.push(
|
|
63
|
+
<ul key={key++} className="my-2 list-disc space-y-1 pl-5">
|
|
64
|
+
{items.map((item, itemIndex) => (
|
|
65
|
+
<li key={itemIndex}>{renderInline(item)}</li>
|
|
66
|
+
))}
|
|
67
|
+
</ul>,
|
|
68
|
+
);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (/^\d+\.\s+/.test(line)) {
|
|
73
|
+
const items: string[] = [];
|
|
74
|
+
while (index < lines.length && /^\d+\.\s+/.test(lines[index])) {
|
|
75
|
+
items.push(lines[index].replace(/^\d+\.\s+/, ''));
|
|
76
|
+
index += 1;
|
|
77
|
+
}
|
|
78
|
+
blocks.push(
|
|
79
|
+
<ol key={key++} className="my-2 list-decimal space-y-1 pl-5">
|
|
80
|
+
{items.map((item, itemIndex) => (
|
|
81
|
+
<li key={itemIndex}>{renderInline(item)}</li>
|
|
82
|
+
))}
|
|
83
|
+
</ol>,
|
|
84
|
+
);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Paragraph: gather consecutive plain lines.
|
|
89
|
+
const paragraph: string[] = [];
|
|
90
|
+
while (index < lines.length && lines[index].trim() !== '' && !isBlockStart(lines[index])) {
|
|
91
|
+
paragraph.push(lines[index]);
|
|
92
|
+
index += 1;
|
|
93
|
+
}
|
|
94
|
+
blocks.push(
|
|
95
|
+
<p key={key++} className="my-2">
|
|
96
|
+
{paragraph.map((text, lineIndex) => (
|
|
97
|
+
<span key={lineIndex}>
|
|
98
|
+
{lineIndex > 0 && <br />}
|
|
99
|
+
{renderInline(text)}
|
|
100
|
+
</span>
|
|
101
|
+
))}
|
|
102
|
+
</p>,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return blocks;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isBlockStart(line: string): boolean {
|
|
110
|
+
return (
|
|
111
|
+
/^(#{1,4})\s+/.test(line) ||
|
|
112
|
+
/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line) ||
|
|
113
|
+
line.startsWith('>') ||
|
|
114
|
+
/^[-*+]\s+/.test(line) ||
|
|
115
|
+
/^\d+\.\s+/.test(line)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function renderHeading(level: number, text: string, key: number): ReactNode {
|
|
120
|
+
const content = renderInline(text);
|
|
121
|
+
const className = {
|
|
122
|
+
1: 'mt-5 mb-2 text-xl font-bold text-zinc-900 first:mt-0',
|
|
123
|
+
2: 'mt-5 mb-1.5 text-base font-bold text-zinc-900 first:mt-0',
|
|
124
|
+
3: 'mt-4 mb-1 text-sm font-semibold text-zinc-900 first:mt-0',
|
|
125
|
+
4: 'mt-3 mb-1 text-[13px] font-semibold tracking-wide text-zinc-500 first:mt-0',
|
|
126
|
+
}[level as 1 | 2 | 3 | 4];
|
|
127
|
+
|
|
128
|
+
if (level === 1) return <h1 key={key} className={className}>{content}</h1>;
|
|
129
|
+
if (level === 2) return <h2 key={key} className={className}>{content}</h2>;
|
|
130
|
+
if (level === 3) return <h3 key={key} className={className}>{content}</h3>;
|
|
131
|
+
return <h4 key={key} className={className}>{content}</h4>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const INLINE_PATTERN = /(`[^`]+`)|(\*\*[^*]+?\*\*)|(\*[^*]+?\*)|(_[^_]+?_)|(\[[^\]]+?\]\([^)]+?\))/g;
|
|
135
|
+
|
|
136
|
+
function renderInline(text: string): ReactNode[] {
|
|
137
|
+
const nodes: ReactNode[] = [];
|
|
138
|
+
const pattern = new RegExp(INLINE_PATTERN.source, 'g');
|
|
139
|
+
let lastIndex = 0;
|
|
140
|
+
let key = 0;
|
|
141
|
+
let match: RegExpExecArray | null;
|
|
142
|
+
|
|
143
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
144
|
+
if (match.index > lastIndex) {
|
|
145
|
+
nodes.push(text.slice(lastIndex, match.index));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const token = match[0];
|
|
149
|
+
if (token.startsWith('`')) {
|
|
150
|
+
nodes.push(
|
|
151
|
+
<code key={key++} className="rounded bg-zinc-100 px-1 py-0.5 font-mono text-[12px] text-zinc-700">
|
|
152
|
+
{token.slice(1, -1)}
|
|
153
|
+
</code>,
|
|
154
|
+
);
|
|
155
|
+
} else if (token.startsWith('**')) {
|
|
156
|
+
nodes.push(<strong key={key++} className="font-semibold text-zinc-900">{token.slice(2, -2)}</strong>);
|
|
157
|
+
} else if (token.startsWith('*')) {
|
|
158
|
+
nodes.push(<em key={key++}>{token.slice(1, -1)}</em>);
|
|
159
|
+
} else if (token.startsWith('_')) {
|
|
160
|
+
nodes.push(<em key={key++}>{token.slice(1, -1)}</em>);
|
|
161
|
+
} else {
|
|
162
|
+
const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(token);
|
|
163
|
+
if (link) {
|
|
164
|
+
nodes.push(
|
|
165
|
+
<a
|
|
166
|
+
key={key++}
|
|
167
|
+
href={link[2]}
|
|
168
|
+
target="_blank"
|
|
169
|
+
rel="noreferrer"
|
|
170
|
+
className="text-[color:var(--ob-accent-700)] underline underline-offset-2"
|
|
171
|
+
>
|
|
172
|
+
{link[1]}
|
|
173
|
+
</a>,
|
|
174
|
+
);
|
|
175
|
+
} else {
|
|
176
|
+
nodes.push(token);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
lastIndex = match.index + token.length;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (lastIndex < text.length) {
|
|
184
|
+
nodes.push(text.slice(lastIndex));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return nodes;
|
|
188
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ONBOARDING_FLOW } from '../config/steps';
|
|
4
|
+
import { cn } from '../lib/cn';
|
|
5
|
+
import { useManGoOnboardingNavigation } from '../ManGoOnboardingNavigation';
|
|
6
|
+
|
|
7
|
+
type StepStatus = 'done' | 'active' | 'upcoming';
|
|
8
|
+
|
|
9
|
+
function statusFor(index: number, activeIndex: number): StepStatus {
|
|
10
|
+
if (index < activeIndex) {
|
|
11
|
+
return 'done';
|
|
12
|
+
}
|
|
13
|
+
return index === activeIndex ? 'active' : 'upcoming';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function Check() {
|
|
17
|
+
return (
|
|
18
|
+
<svg viewBox="0 0 16 16" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth={2.5} aria-hidden>
|
|
19
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M3.5 8.5l3 3 6-7" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DOT_BASE = 'flex flex-shrink-0 items-center justify-center rounded-full transition-all duration-300';
|
|
25
|
+
|
|
26
|
+
function dotClass(status: StepStatus): string {
|
|
27
|
+
return cn(
|
|
28
|
+
DOT_BASE,
|
|
29
|
+
status === 'active' &&
|
|
30
|
+
'bg-[color:var(--ob-brand-blue)] text-[color:var(--ob-ink)] font-bold shadow-[0_0_0_4px_rgba(122,235,255,0.20)]',
|
|
31
|
+
status === 'done' && 'bg-white/20 text-white',
|
|
32
|
+
status === 'upcoming' && 'border-2 border-white/40 text-white/60',
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Wizard progress shown inside the accent rail — clickable so steps double as navigation.
|
|
37
|
+
* Vertical on desktop, a compact row on mobile. */
|
|
38
|
+
export function RailStepper({
|
|
39
|
+
activeIndex,
|
|
40
|
+
orientation = 'vertical',
|
|
41
|
+
}: {
|
|
42
|
+
readonly activeIndex: number;
|
|
43
|
+
readonly orientation?: 'vertical' | 'horizontal';
|
|
44
|
+
}) {
|
|
45
|
+
const { navigateToPath } = useManGoOnboardingNavigation();
|
|
46
|
+
|
|
47
|
+
if (orientation === 'horizontal') {
|
|
48
|
+
return (
|
|
49
|
+
<ol className="flex items-center" aria-label="Průběh onboardingu">
|
|
50
|
+
{ONBOARDING_FLOW.map((step, index) => {
|
|
51
|
+
const status = statusFor(index, activeIndex);
|
|
52
|
+
const isLast = index === ONBOARDING_FLOW.length - 1;
|
|
53
|
+
return (
|
|
54
|
+
<Fragment key={step.id}>
|
|
55
|
+
<li>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
onClick={() => navigateToPath(step.path)}
|
|
59
|
+
aria-current={status === 'active' ? 'step' : undefined}
|
|
60
|
+
className="flex items-center gap-2 rounded-lg p-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
61
|
+
>
|
|
62
|
+
<span className={cn(dotClass(status), 'h-6 w-6 text-[11px]')}>
|
|
63
|
+
{status === 'done' ? <Check /> : index + 1}
|
|
64
|
+
</span>
|
|
65
|
+
<span
|
|
66
|
+
className={cn(
|
|
67
|
+
'whitespace-nowrap text-[13px] font-medium text-white',
|
|
68
|
+
status !== 'active' && 'hidden',
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
{step.label}
|
|
72
|
+
</span>
|
|
73
|
+
</button>
|
|
74
|
+
</li>
|
|
75
|
+
{!isLast && <span aria-hidden className="mx-1 h-px w-3.5 bg-white/30" />}
|
|
76
|
+
</Fragment>
|
|
77
|
+
);
|
|
78
|
+
})}
|
|
79
|
+
</ol>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<ol className="relative flex flex-col gap-0.5" aria-label="Průběh onboardingu">
|
|
85
|
+
<span aria-hidden className="absolute bottom-4 left-[11px] top-4 w-0.5 bg-white/20" />
|
|
86
|
+
{ONBOARDING_FLOW.map((step, index) => {
|
|
87
|
+
const status = statusFor(index, activeIndex);
|
|
88
|
+
return (
|
|
89
|
+
<li key={step.id} className="relative">
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
onClick={() => navigateToPath(step.path)}
|
|
93
|
+
aria-current={status === 'active' ? 'step' : undefined}
|
|
94
|
+
className="group -mx-2 flex w-full items-center gap-3 rounded-lg px-2 py-1.5 text-left transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
95
|
+
>
|
|
96
|
+
<span className={cn(dotClass(status), 'h-6 w-6 text-[12px]')}>
|
|
97
|
+
{status === 'done' ? <Check /> : index + 1}
|
|
98
|
+
</span>
|
|
99
|
+
<span
|
|
100
|
+
className={cn(
|
|
101
|
+
'text-sm transition-colors',
|
|
102
|
+
status === 'active' && 'font-semibold text-white',
|
|
103
|
+
status === 'done' && 'font-medium text-white/80 group-hover:text-white',
|
|
104
|
+
status === 'upcoming' && 'text-white/55 group-hover:text-white/80',
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
{step.label}
|
|
108
|
+
</span>
|
|
109
|
+
</button>
|
|
110
|
+
</li>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
113
|
+
</ol>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/cn';
|
|
4
|
+
import { Card } from './ui/Card';
|
|
5
|
+
|
|
6
|
+
export function StepHeader({
|
|
7
|
+
title,
|
|
8
|
+
subtitle,
|
|
9
|
+
eyebrow,
|
|
10
|
+
}: {
|
|
11
|
+
readonly title: string;
|
|
12
|
+
readonly subtitle?: ReactNode;
|
|
13
|
+
/** Optional small uppercase label above the title. */
|
|
14
|
+
readonly eyebrow?: ReactNode;
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<header className="mb-8">
|
|
18
|
+
{eyebrow && (
|
|
19
|
+
<div className="mb-2 text-[13px] font-semibold text-[color:var(--ob-accent-600)]">{eyebrow}</div>
|
|
20
|
+
)}
|
|
21
|
+
<h1 className="ob-display text-balance text-[28px] font-bold tracking-tight text-zinc-900">{title}</h1>
|
|
22
|
+
{subtitle && <p className="mt-2.5 max-w-prose text-sm leading-relaxed text-zinc-500">{subtitle}</p>}
|
|
23
|
+
</header>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** White padded card used as the body of form-like steps. */
|
|
28
|
+
export function StepCard({ className, children }: { readonly className?: string; readonly children: ReactNode }) {
|
|
29
|
+
return <Card className={cn('p-8 sm:p-10', className)}>{children}</Card>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Section heading inside a card — small uppercase label for grouped content. */
|
|
33
|
+
export function SectionLabel({ className, children }: { readonly className?: string; readonly children: ReactNode }) {
|
|
34
|
+
return (
|
|
35
|
+
<div className={cn('text-xs font-semibold text-zinc-400', className)}>{children}</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Footer row with a divider — typically "back" on the left, primary action on the right. */
|
|
40
|
+
export function StepFooter({ left, right }: { readonly left?: ReactNode; readonly right?: ReactNode }) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="mt-9 flex items-center justify-between gap-4 border-t border-zinc-100 pt-6">
|
|
43
|
+
<div>{left}</div>
|
|
44
|
+
<div className="flex items-center gap-3">{right}</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/TestChat.tsx
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
import type { ChatMessage } from '../types';
|
|
7
|
+
import { cn } from '../lib/cn';
|
|
8
|
+
import { Badge } from './ui/Badge';
|
|
9
|
+
import { Button } from './ui/Button';
|
|
10
|
+
import { CONTROL } from './ui/Field';
|
|
11
|
+
|
|
12
|
+
const GREETING = '👋 Jsem připravený. Vložte zadání — třeba popis e-mailu od zákazníka — a ukážu vám, jak odpovím.';
|
|
13
|
+
|
|
14
|
+
type TestChatProps = {
|
|
15
|
+
readonly messages: readonly ChatMessage[];
|
|
16
|
+
readonly isSending: boolean;
|
|
17
|
+
readonly onSend: (text: string) => void;
|
|
18
|
+
readonly onStop?: () => void;
|
|
19
|
+
readonly onRetry?: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function Bubble({
|
|
23
|
+
role,
|
|
24
|
+
children,
|
|
25
|
+
footer,
|
|
26
|
+
}: {
|
|
27
|
+
readonly role: 'user' | 'agent';
|
|
28
|
+
readonly children: ReactNode;
|
|
29
|
+
readonly footer?: ReactNode;
|
|
30
|
+
}) {
|
|
31
|
+
return (
|
|
32
|
+
<div className={cn('group flex max-w-[85%] flex-col gap-1', role === 'user' ? 'items-end self-end' : 'items-start self-start')}>
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
'whitespace-pre-wrap rounded-2xl px-3.5 py-2.5 text-[13px] leading-relaxed',
|
|
36
|
+
role === 'user'
|
|
37
|
+
? 'rounded-br-sm bg-[color:var(--ob-accent-600)] text-white shadow-[var(--ob-shadow-accent)]'
|
|
38
|
+
: 'rounded-bl-sm bg-zinc-100 text-zinc-800',
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
43
|
+
{footer}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function TestChat({ messages, isSending, onSend, onStop, onRetry }: TestChatProps) {
|
|
49
|
+
const [text, setText] = useState('');
|
|
50
|
+
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
51
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });
|
|
55
|
+
}, [messages, isSending]);
|
|
56
|
+
|
|
57
|
+
function submit() {
|
|
58
|
+
const trimmed = text.trim();
|
|
59
|
+
if (!trimmed || isSending) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
onSend(trimmed);
|
|
63
|
+
setText('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function copy(id: string, content: string) {
|
|
67
|
+
void navigator.clipboard?.writeText(content).then(() => {
|
|
68
|
+
setCopiedId(id);
|
|
69
|
+
setTimeout(() => setCopiedId((current) => (current === id ? null : current)), 1500);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const lastMessage = messages[messages.length - 1];
|
|
74
|
+
const isRetryShown = Boolean(onRetry) && !isSending && lastMessage?.role === 'agent';
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className="flex h-full flex-col overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-[var(--ob-shadow-md)]">
|
|
78
|
+
<div className="flex items-center gap-2 border-b border-zinc-200 bg-zinc-50/80 px-4 py-2.5 text-[13px] font-medium text-zinc-600">
|
|
79
|
+
<span className="relative flex h-2 w-2" aria-hidden>
|
|
80
|
+
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
|
81
|
+
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
|
|
82
|
+
</span>
|
|
83
|
+
Testovací prostředí
|
|
84
|
+
<span className="ml-auto">
|
|
85
|
+
<Badge tone="success" dot>
|
|
86
|
+
Naživo
|
|
87
|
+
</Badge>
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div ref={scrollRef} className="flex max-h-[420px] min-h-[300px] flex-1 flex-col gap-3.5 overflow-y-auto p-4">
|
|
92
|
+
<Bubble role="agent">{GREETING}</Bubble>
|
|
93
|
+
{messages.map((message) =>
|
|
94
|
+
message.role === 'agent' ? (
|
|
95
|
+
<Bubble
|
|
96
|
+
key={message.id}
|
|
97
|
+
role="agent"
|
|
98
|
+
footer={
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={() => copy(message.id, message.content)}
|
|
102
|
+
className="text-[11px] font-medium text-zinc-400 opacity-0 transition-opacity hover:text-zinc-700 focus-visible:opacity-100 group-hover:opacity-100"
|
|
103
|
+
>
|
|
104
|
+
{copiedId === message.id ? '✓ Zkopírováno' : 'Kopírovat'}
|
|
105
|
+
</button>
|
|
106
|
+
}
|
|
107
|
+
>
|
|
108
|
+
{message.content}
|
|
109
|
+
</Bubble>
|
|
110
|
+
) : (
|
|
111
|
+
<Bubble key={message.id} role="user">
|
|
112
|
+
{message.content}
|
|
113
|
+
</Bubble>
|
|
114
|
+
),
|
|
115
|
+
)}
|
|
116
|
+
{isSending && (
|
|
117
|
+
<Bubble role="agent">
|
|
118
|
+
<span className="inline-flex gap-1">
|
|
119
|
+
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-zinc-400 [animation-delay:-0.2s]" />
|
|
120
|
+
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-zinc-400 [animation-delay:-0.1s]" />
|
|
121
|
+
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-zinc-400" />
|
|
122
|
+
</span>
|
|
123
|
+
</Bubble>
|
|
124
|
+
)}
|
|
125
|
+
{isRetryShown && (
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onClick={onRetry}
|
|
129
|
+
className="mx-auto mt-1 inline-flex items-center gap-1.5 rounded-full border border-zinc-200 bg-white px-3 py-1 text-xs font-medium text-zinc-500 transition-colors hover:border-zinc-300 hover:text-zinc-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--ob-ring)]"
|
|
130
|
+
>
|
|
131
|
+
<span aria-hidden>↻</span> Zkusit znovu
|
|
132
|
+
</button>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div className="flex gap-2 border-t border-zinc-200 bg-zinc-50/50 p-3">
|
|
137
|
+
<input
|
|
138
|
+
type="text"
|
|
139
|
+
value={text}
|
|
140
|
+
aria-label="Testovací zadání pro agenta"
|
|
141
|
+
placeholder="Vložte testovací zadání pro agenta…"
|
|
142
|
+
disabled={isSending}
|
|
143
|
+
onChange={(event) => setText(event.target.value)}
|
|
144
|
+
onKeyDown={(event) => event.key === 'Enter' && submit()}
|
|
145
|
+
className={cn(CONTROL, 'py-2 disabled:opacity-60')}
|
|
146
|
+
/>
|
|
147
|
+
{isSending ? (
|
|
148
|
+
<Button variant="outline" size="sm" onClick={onStop}>
|
|
149
|
+
Stop
|
|
150
|
+
</Button>
|
|
151
|
+
) : (
|
|
152
|
+
<Button size="sm" onClick={submit} disabled={text.trim().length === 0}>
|
|
153
|
+
Odeslat
|
|
154
|
+
</Button>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|