feat: add AI session prompt export
This commit is contained in:
+91
-26
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import './index.css'
|
||||
import { createMarkdownPackage, createJsonExport, createPulseJsonl } from './features/export/exporters'
|
||||
import { createAgentSessionPrompt, createMarkdownPackage, createJsonExport, createPulseJsonl } from './features/export/exporters'
|
||||
import { loadAppState, replaceAppState, saveAppState, validateAppState } from './store/storage'
|
||||
import { fetchRemoteState, pushRemoteState } from './store/remote'
|
||||
import { FEATURE_COLUMNS, FEATURE_PRIORITIES, FEATURE_STATUSES, PULSE_TYPES, RISK_LEVELS } from './store/types'
|
||||
@@ -15,6 +15,8 @@ const TABS: Array<{ key: TabKey; label: string }> = [
|
||||
{ key: 'export', label: 'Export' },
|
||||
]
|
||||
|
||||
const PROMPT_TARGETS = ['OpenClaw', 'Claude Code', 'Codex', 'Generic Agent'] as const
|
||||
|
||||
const initialFeatureDraft = {
|
||||
title: '',
|
||||
description: '',
|
||||
@@ -74,6 +76,8 @@ function App() {
|
||||
const [pulseTypeFilter, setPulseTypeFilter] = useState('all')
|
||||
const [pulseFeatureFilter, setPulseFeatureFilter] = useState('all')
|
||||
const [pulseSourceFilter, setPulseSourceFilter] = useState('all')
|
||||
const [promptTarget, setPromptTarget] = useState<(typeof PROMPT_TARGETS)[number]>('OpenClaw')
|
||||
const [promptFeatureId, setPromptFeatureId] = useState('')
|
||||
const [backendMode, setBackendMode] = useState<'connecting' | 'appwrite' | 'local-cache'>('connecting')
|
||||
const hasHydratedRemote = useRef(false)
|
||||
const initialLocalStateRef = useRef(appState)
|
||||
@@ -167,6 +171,10 @@ function App() {
|
||||
)
|
||||
|
||||
const markdownPackage = useMemo(() => createMarkdownPackage(appState), [appState])
|
||||
const sessionPrompt = useMemo(
|
||||
() => createAgentSessionPrompt(appState, { featureId: promptFeatureId || undefined, target: promptTarget }),
|
||||
[appState, promptFeatureId, promptTarget],
|
||||
)
|
||||
|
||||
const updateProject = (field: keyof AppState['project'], value: string) => {
|
||||
setAppState((current) => ({
|
||||
@@ -459,6 +467,15 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
const copySessionPrompt = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(sessionPrompt)
|
||||
setStatusMessage('AI session prompt copied to clipboard.')
|
||||
} catch {
|
||||
setStatusMessage('Clipboard copy failed. Browser said no.')
|
||||
}
|
||||
}
|
||||
|
||||
const handleImport = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
@@ -974,32 +991,80 @@ function App() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="card list-card">
|
||||
<div className="section-heading compact">
|
||||
<div>
|
||||
<h3>Markdown Package</h3>
|
||||
<p>`CLAUDE_CONTEXT.md` is the decision-boundary file. Keep it sharp.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list-stack markdown-list">
|
||||
{Object.entries(markdownPackage).map(([filename, content]) => (
|
||||
<div key={filename} className="markdown-card">
|
||||
<div className="item-card-header">
|
||||
<strong>{filename}</strong>
|
||||
<div className="button-inline-row">
|
||||
<button type="button" className="ghost small" onClick={() => copyMarkdown(filename)}>
|
||||
Copy
|
||||
</button>
|
||||
<button type="button" className="ghost small" onClick={() => downloadText(filename, content)}>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre>{content}</pre>
|
||||
<div className="list-stack">
|
||||
<section className="card list-card">
|
||||
<div className="section-heading compact">
|
||||
<div>
|
||||
<h3>AI Session Prompt</h3>
|
||||
<p>Pick a focus feature and hand a sharp brief to your coding agent instead of pasting the whole kitchen sink.</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div className="filter-row">
|
||||
<label>
|
||||
Target
|
||||
<select value={promptTarget} onChange={(event) => setPromptTarget(event.target.value as (typeof PROMPT_TARGETS)[number])}>
|
||||
{PROMPT_TARGETS.map((target) => (
|
||||
<option key={target} value={target}>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Focus Feature
|
||||
<select value={promptFeatureId} onChange={(event) => setPromptFeatureId(event.target.value)}>
|
||||
<option value="">Auto-pick first Now feature</option>
|
||||
{appState.features.map((feature) => (
|
||||
<option key={feature.id} value={feature.id}>
|
||||
{feature.title} · {columnLabels[feature.column]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className="button-inline-row">
|
||||
<button type="button" onClick={copySessionPrompt}>
|
||||
Copy Prompt
|
||||
</button>
|
||||
<button type="button" className="ghost" onClick={() => downloadText('AI_SESSION_PROMPT.md', sessionPrompt)}>
|
||||
Download Prompt
|
||||
</button>
|
||||
</div>
|
||||
<div className="markdown-card">
|
||||
<div className="item-card-header">
|
||||
<strong>AI_SESSION_PROMPT.md</strong>
|
||||
</div>
|
||||
<pre>{sessionPrompt}</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="card list-card">
|
||||
<div className="section-heading compact">
|
||||
<div>
|
||||
<h3>Markdown Package</h3>
|
||||
<p>`CLAUDE_CONTEXT.md` is the decision-boundary file. Keep it sharp.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="list-stack markdown-list">
|
||||
{Object.entries(markdownPackage).map(([filename, content]) => (
|
||||
<div key={filename} className="markdown-card">
|
||||
<div className="item-card-header">
|
||||
<strong>{filename}</strong>
|
||||
<div className="button-inline-row">
|
||||
<button type="button" className="ghost small" onClick={() => copyMarkdown(filename)}>
|
||||
Copy
|
||||
</button>
|
||||
<button type="button" className="ghost small" onClick={() => downloadText(filename, content)}>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre>{content}</pre>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user