WIP - ai process builder codex attempt
This commit is contained in:
12
frontend/ai-processes-editor/index.html
Normal file
12
frontend/ai-processes-editor/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AI Process Builder</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
frontend/ai-processes-editor/package.json
Normal file
23
frontend/ai-processes-editor/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "ai-processes-editor",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 5174",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --port 5174"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
43
frontend/ai-processes-editor/src/App.tsx
Normal file
43
frontend/ai-processes-editor/src/App.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState } from '@xyflow/react'
|
||||
import '@xyflow/react/dist/style.css'
|
||||
import './styles.css'
|
||||
|
||||
const initialNodes = [
|
||||
{ id: 'start', data: { label: 'Start' }, position: { x: 0, y: 0 } },
|
||||
{ id: 'llm', data: { label: 'LLM Decision' }, position: { x: 220, y: 0 } },
|
||||
{ id: 'tool', data: { label: 'Tool Node' }, position: { x: 440, y: 0 } },
|
||||
{ id: 'end', data: { label: 'End' }, position: { x: 660, y: 0 } },
|
||||
]
|
||||
|
||||
const initialEdges = [
|
||||
{ id: 'e1-2', source: 'start', target: 'llm' },
|
||||
{ id: 'e2-3', source: 'llm', target: 'tool' },
|
||||
{ id: 'e3-4', source: 'tool', target: 'end' },
|
||||
]
|
||||
|
||||
export const App = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
|
||||
|
||||
return (
|
||||
<div className="editor-shell">
|
||||
<header className="editor-header">
|
||||
<h1>AI Process Builder</h1>
|
||||
<p>Design tenant workflows with deterministic execution.</p>
|
||||
</header>
|
||||
<div className="editor-canvas">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
fitView
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
7
frontend/ai-processes-editor/src/main.tsx
Normal file
7
frontend/ai-processes-editor/src/main.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { App } from './App'
|
||||
|
||||
const root = document.getElementById('root')
|
||||
if (root) {
|
||||
createRoot(root).render(<App />)
|
||||
}
|
||||
33
frontend/ai-processes-editor/src/styles.css
Normal file
33
frontend/ai-processes-editor/src/styles.css
Normal file
@@ -0,0 +1,33 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.editor-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.editor-header h1 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.editor-header p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.editor-canvas {
|
||||
flex: 1;
|
||||
background: #f8fafc;
|
||||
}
|
||||
9
frontend/ai-processes-editor/vite.config.ts
Normal file
9
frontend/ai-processes-editor/vite.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 5174,
|
||||
},
|
||||
})
|
||||
@@ -16,6 +16,12 @@ const messages = ref<{ role: 'user' | 'assistant'; text: string }[]>([])
|
||||
const sending = ref(false)
|
||||
const route = useRoute()
|
||||
const { api } = useApi()
|
||||
const sessionId = ref<string | null>(null)
|
||||
|
||||
const getTenantId = () => {
|
||||
if (!import.meta.client) return 'tenant1'
|
||||
return localStorage.getItem('tenantId') || 'tenant1'
|
||||
}
|
||||
|
||||
const buildContext = () => {
|
||||
const recordId = route.params.recordId ? String(route.params.recordId) : undefined
|
||||
@@ -43,27 +49,39 @@ const handleSend = async () => {
|
||||
|
||||
try {
|
||||
const history = messages.value.slice(0, -1).slice(-6)
|
||||
const response = await api.post('/ai/chat', {
|
||||
const response = await api.post(`/tenants/${getTenantId()}/ai-chat/messages`, {
|
||||
message,
|
||||
history,
|
||||
context: buildContext(),
|
||||
sessionId: sessionId.value || undefined,
|
||||
})
|
||||
|
||||
if (response.sessionId) {
|
||||
sessionId.value = response.sessionId
|
||||
}
|
||||
|
||||
if (response.reply) {
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: response.reply,
|
||||
})
|
||||
if (response.action === 'create_record') {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ai-record-created', {
|
||||
detail: {
|
||||
objectApiName: buildContext().objectApiName,
|
||||
record: response.record,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: response.reply || 'Let me know what else you need.',
|
||||
text: 'Process started. I will post updates as soon as they are ready.',
|
||||
})
|
||||
|
||||
if (response.action === 'create_record') {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ai-record-created', {
|
||||
detail: {
|
||||
objectApiName: buildContext().objectApiName,
|
||||
record: response.record,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to send AI chat message:', error)
|
||||
messages.value.push({
|
||||
|
||||
52
frontend/components/ai-processes/NeedInputForm.vue
Normal file
52
frontend/components/ai-processes/NeedInputForm.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="rounded-lg border border-amber-200 bg-amber-50 p-4">
|
||||
<p class="mb-3 text-sm font-semibold text-amber-900">{{ prompt }}</p>
|
||||
<form class="space-y-3" @submit.prevent="submit">
|
||||
<div v-for="field in fields" :key="field.name" class="space-y-1">
|
||||
<label class="text-xs font-medium text-slate-600">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form[field.name]"
|
||||
class="w-full rounded border border-slate-300 px-3 py-2 text-sm"
|
||||
:type="field.type === 'number' ? 'number' : 'text'"
|
||||
:required="field.required"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded bg-slate-900 px-4 py-2 text-sm font-semibold text-white"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
schema: Record<string, any>
|
||||
prompt: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'submit', payload: Record<string, unknown>): void
|
||||
}>()
|
||||
|
||||
const form = reactive<Record<string, string>>({})
|
||||
|
||||
const fields = computed(() => {
|
||||
const properties = props.schema?.properties || {}
|
||||
const required = props.schema?.required || []
|
||||
return Object.entries(properties).map(([name, config]: [string, any]) => ({
|
||||
name,
|
||||
label: config.title || name,
|
||||
type: config.type || 'string',
|
||||
required: required.includes(name),
|
||||
}))
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
emit('submit', { ...form })
|
||||
}
|
||||
</script>
|
||||
19
frontend/components/ai-processes/ReactFlowIframe.vue
Normal file
19
frontend/components/ai-processes/ReactFlowIframe.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="rounded-lg border border-slate-200 bg-white shadow">
|
||||
<div class="border-b border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700">
|
||||
Process Graph Editor
|
||||
</div>
|
||||
<iframe
|
||||
class="h-[640px] w-full"
|
||||
:src="editorUrl"
|
||||
title="AI Process Builder"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const config = useRuntimeConfig()
|
||||
const editorUrl = computed(() =>
|
||||
config.public.aiProcessEditorUrl || 'http://localhost:5174'
|
||||
)
|
||||
</script>
|
||||
@@ -26,6 +26,8 @@ export default defineNuxtConfig({
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || 'http://localhost:3000',
|
||||
aiProcessEditorUrl:
|
||||
process.env.NUXT_PUBLIC_AI_PROCESS_EDITOR_URL || 'http://localhost:5174',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
14
frontend/pages/admin/ai-processes.vue
Normal file
14
frontend/pages/admin/ai-processes.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div class="space-y-6 p-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900">
|
||||
AI Process Builder
|
||||
</h1>
|
||||
<p class="text-sm text-slate-600">
|
||||
Define tenant-scoped process graphs and publish versions for orchestration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ReactFlowIframe />
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user