flowquery 1.0.8 → 1.0.10
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/dist/flowquery.min.js +1 -1
- package/dist/parsing/functions/function_factory.d.ts +2 -0
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +2 -0
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/functions/keys.d.ts +7 -0
- package/dist/parsing/functions/keys.d.ts.map +1 -0
- package/dist/parsing/functions/keys.js +42 -0
- package/dist/parsing/functions/keys.js.map +1 -0
- package/dist/parsing/functions/type.d.ts +7 -0
- package/dist/parsing/functions/type.d.ts.map +1 -0
- package/dist/parsing/functions/type.js +49 -0
- package/dist/parsing/functions/type.js.map +1 -0
- package/docs/flowquery.min.js +1 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/misc/apps/RAG/package.json +3 -1
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.css +172 -0
- package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +312 -0
- package/misc/apps/RAG/src/components/ChatContainer.tsx +159 -112
- package/misc/apps/RAG/src/components/ChatInput.tsx +58 -44
- package/misc/apps/RAG/src/components/ChatMessage.tsx +186 -101
- package/misc/apps/RAG/src/components/FlowQueryAgent.ts +50 -6
- package/misc/apps/RAG/src/components/FlowQueryRunner.css +9 -0
- package/misc/apps/RAG/src/components/FlowQueryRunner.tsx +44 -5
- package/misc/apps/RAG/src/components/index.ts +4 -0
- package/misc/apps/RAG/src/plugins/index.ts +6 -4
- package/misc/apps/RAG/src/plugins/loaders/CatFacts.ts +1 -2
- package/misc/apps/RAG/src/plugins/loaders/FetchJson.ts +1 -2
- package/misc/apps/RAG/src/plugins/loaders/Form.ts +578 -0
- package/misc/apps/RAG/src/plugins/loaders/Llm.ts +1 -2
- package/misc/apps/RAG/src/plugins/loaders/MockData.ts +2 -4
- package/misc/apps/RAG/src/plugins/loaders/Table.ts +271 -0
- package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +12 -0
- package/package.json +1 -1
- package/src/parsing/functions/function_factory.ts +2 -0
- package/src/parsing/functions/keys.ts +31 -0
- package/src/parsing/functions/type.ts +39 -0
- package/tests/compute/runner.test.ts +8 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
2
|
import { Body1, Spinner, Button, Tooltip } from '@fluentui/react-components';
|
|
3
3
|
import { PersonFilled, BotFilled, Play16Regular } from '@fluentui/react-icons';
|
|
4
4
|
import { FlowQueryRunner } from './FlowQueryRunner';
|
|
5
|
+
import { AdaptiveCardRenderer, isAdaptiveCard } from './AdaptiveCardRenderer';
|
|
5
6
|
import './ChatMessage.css';
|
|
6
7
|
|
|
7
8
|
export interface Message {
|
|
@@ -10,6 +11,10 @@ export interface Message {
|
|
|
10
11
|
content: string;
|
|
11
12
|
timestamp: Date;
|
|
12
13
|
isStreaming?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Optional Adaptive Card payload to render instead of or alongside text content
|
|
16
|
+
*/
|
|
17
|
+
adaptiveCard?: Record<string, unknown>;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
interface ChatMessageProps {
|
|
@@ -35,118 +40,198 @@ function extractFlowQueryBlocks(content: string): string[] {
|
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
|
-
*
|
|
43
|
+
* Extract JSON blocks from content and check if any are Adaptive Cards
|
|
39
44
|
*/
|
|
40
|
-
|
|
41
|
-
const
|
|
45
|
+
function extractAdaptiveCards(content: string): Record<string, unknown>[] {
|
|
46
|
+
const cards: Record<string, unknown>[] = [];
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
// Look for ```json blocks that might contain Adaptive Cards
|
|
49
|
+
const jsonRegex = /```json\n([\s\S]*?)```/gi;
|
|
50
|
+
let match;
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
while ((match = jsonRegex.exec(content)) !== null) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(match[1]);
|
|
55
|
+
if (isAdaptiveCard(parsed)) {
|
|
56
|
+
cards.push(parsed);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Not valid JSON, skip
|
|
60
|
+
}
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
return cards;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface MessageContentProps {
|
|
67
|
+
content: string;
|
|
68
|
+
isStreaming?: boolean;
|
|
69
|
+
adaptiveCard?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface MessageContentState {
|
|
73
|
+
runnerQuery: string | null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Renders message content with FlowQuery code blocks enhanced with run buttons,
|
|
78
|
+
* and optionally renders Adaptive Cards.
|
|
79
|
+
*/
|
|
80
|
+
class MessageContent extends Component<MessageContentProps, MessageContentState> {
|
|
81
|
+
constructor(props: MessageContentProps) {
|
|
82
|
+
super(props);
|
|
83
|
+
this.state = {
|
|
84
|
+
runnerQuery: null
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private setRunnerQuery = (query: string | null) => {
|
|
89
|
+
this.setState({ runnerQuery: query });
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
private getFlowQueryBlocks(): string[] {
|
|
93
|
+
return extractFlowQueryBlocks(this.props.content);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private getEmbeddedAdaptiveCards(): Record<string, unknown>[] {
|
|
97
|
+
return extractAdaptiveCards(this.props.content);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private getAllAdaptiveCards(): Record<string, unknown>[] {
|
|
101
|
+
const cards: Record<string, unknown>[] = [];
|
|
102
|
+
if (this.props.adaptiveCard) {
|
|
103
|
+
cards.push(this.props.adaptiveCard);
|
|
104
|
+
}
|
|
105
|
+
cards.push(...this.getEmbeddedAdaptiveCards());
|
|
106
|
+
return cards;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
const { content, isStreaming } = this.props;
|
|
111
|
+
const { runnerQuery } = this.state;
|
|
112
|
+
|
|
113
|
+
const flowQueryBlocks = this.getFlowQueryBlocks();
|
|
114
|
+
const allAdaptiveCards = this.getAllAdaptiveCards();
|
|
115
|
+
|
|
116
|
+
// If there are no FlowQuery blocks, render plain content (possibly with Adaptive Cards)
|
|
117
|
+
if (flowQueryBlocks.length === 0) {
|
|
118
|
+
return (
|
|
119
|
+
<>
|
|
120
|
+
{content}
|
|
121
|
+
{allAdaptiveCards.map((card, index) => (
|
|
122
|
+
<AdaptiveCardRenderer key={`card-${index}`} card={card} />
|
|
123
|
+
))}
|
|
124
|
+
{isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Split content by FlowQuery code blocks and render with buttons
|
|
130
|
+
const parts: React.ReactNode[] = [];
|
|
131
|
+
let lastIndex = 0;
|
|
132
|
+
const regex = /```flowquery\n([\s\S]*?)```/gi;
|
|
133
|
+
let match;
|
|
134
|
+
let partIndex = 0;
|
|
135
|
+
|
|
136
|
+
while ((match = regex.exec(content)) !== null) {
|
|
137
|
+
// Add text before the code block
|
|
138
|
+
if (match.index > lastIndex) {
|
|
139
|
+
parts.push(
|
|
140
|
+
<span key={`text-${partIndex}`}>
|
|
141
|
+
{content.slice(lastIndex, match.index)}
|
|
142
|
+
</span>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const query = match[1]?.trim() || '';
|
|
147
|
+
|
|
148
|
+
// Add the code block with a run button and </> link
|
|
149
|
+
parts.push(
|
|
150
|
+
<div key={`code-${partIndex}`} className="flowquery-code-block">
|
|
151
|
+
<div className="flowquery-code-header">
|
|
152
|
+
<span className="flowquery-code-label">flowquery</span>
|
|
153
|
+
<div className="flowquery-code-actions">
|
|
154
|
+
<Tooltip content="Run in FlowQuery Runner" relationship="label">
|
|
155
|
+
<Button
|
|
156
|
+
appearance="subtle"
|
|
157
|
+
size="small"
|
|
158
|
+
icon={<Play16Regular />}
|
|
159
|
+
className="flowquery-run-button"
|
|
160
|
+
onClick={() => this.setRunnerQuery(query)}
|
|
161
|
+
>
|
|
162
|
+
Run
|
|
163
|
+
</Button>
|
|
164
|
+
</Tooltip>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<pre className="flowquery-code-content">
|
|
168
|
+
<code>{query}</code>
|
|
169
|
+
</pre>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
lastIndex = match.index + match[0].length;
|
|
174
|
+
partIndex++;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add remaining text after the last code block
|
|
178
|
+
if (lastIndex < content.length) {
|
|
65
179
|
parts.push(
|
|
66
180
|
<span key={`text-${partIndex}`}>
|
|
67
|
-
{content.slice(lastIndex
|
|
181
|
+
{content.slice(lastIndex)}
|
|
68
182
|
</span>
|
|
69
183
|
);
|
|
70
184
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
</Button>
|
|
90
|
-
</Tooltip>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
<pre className="flowquery-code-content">
|
|
94
|
-
<code>{query}</code>
|
|
95
|
-
</pre>
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
lastIndex = match.index + match[0].length;
|
|
100
|
-
partIndex++;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Add remaining text after the last code block
|
|
104
|
-
if (lastIndex < content.length) {
|
|
105
|
-
parts.push(
|
|
106
|
-
<span key={`text-${partIndex}`}>
|
|
107
|
-
{content.slice(lastIndex)}
|
|
108
|
-
</span>
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<>
|
|
188
|
+
{parts}
|
|
189
|
+
{allAdaptiveCards.map((card, index) => (
|
|
190
|
+
<AdaptiveCardRenderer key={`card-${index}`} card={card} />
|
|
191
|
+
))}
|
|
192
|
+
{isStreaming && <Spinner size="tiny" className="streaming-indicator" />}
|
|
193
|
+
{runnerQuery !== null && (
|
|
194
|
+
<FlowQueryRunner
|
|
195
|
+
initialQuery={runnerQuery}
|
|
196
|
+
open={true}
|
|
197
|
+
onOpenChange={(open) => {
|
|
198
|
+
if (!open) this.setRunnerQuery(null);
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
)}
|
|
202
|
+
</>
|
|
109
203
|
);
|
|
110
204
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}}
|
|
123
|
-
/>
|
|
124
|
-
)}
|
|
125
|
-
</>
|
|
126
|
-
);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
export const ChatMessage: React.FC<ChatMessageProps> = ({ message }) => {
|
|
130
|
-
const isUser = message.role === 'user';
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<div className={`chat-message ${isUser ? 'chat-message-user' : 'chat-message-assistant'}`}>
|
|
134
|
-
<div className="chat-message-avatar">
|
|
135
|
-
{isUser ? <PersonFilled /> : <BotFilled />}
|
|
136
|
-
</div>
|
|
137
|
-
<div className="chat-message-content">
|
|
138
|
-
<div className="chat-message-header">
|
|
139
|
-
<Body1 className="chat-message-role">
|
|
140
|
-
{isUser ? 'You' : 'Assistant'}
|
|
141
|
-
</Body1>
|
|
142
|
-
<span className="chat-message-time">
|
|
143
|
-
{message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
|
144
|
-
</span>
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export class ChatMessage extends Component<ChatMessageProps> {
|
|
208
|
+
render() {
|
|
209
|
+
const { message } = this.props;
|
|
210
|
+
const isUser = message.role === 'user';
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className={`chat-message ${isUser ? 'chat-message-user' : 'chat-message-assistant'}`}>
|
|
214
|
+
<div className="chat-message-avatar">
|
|
215
|
+
{isUser ? <PersonFilled /> : <BotFilled />}
|
|
145
216
|
</div>
|
|
146
|
-
<div className="chat-message-
|
|
147
|
-
<
|
|
217
|
+
<div className="chat-message-content">
|
|
218
|
+
<div className="chat-message-header">
|
|
219
|
+
<Body1 className="chat-message-role">
|
|
220
|
+
{isUser ? 'You' : 'Assistant'}
|
|
221
|
+
</Body1>
|
|
222
|
+
<span className="chat-message-time">
|
|
223
|
+
{message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
226
|
+
<div className="chat-message-text">
|
|
227
|
+
<MessageContent
|
|
228
|
+
content={message.content}
|
|
229
|
+
isStreaming={message.isStreaming}
|
|
230
|
+
adaptiveCard={message.adaptiveCard}
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
148
233
|
</div>
|
|
149
234
|
</div>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { llm, llmStream, LlmOptions, LlmResponse } from '../plugins/loaders/Llm';
|
|
12
12
|
import { FlowQueryExecutor, FlowQueryExecutionResult } from '../utils/FlowQueryExecutor';
|
|
13
13
|
import { extractFlowQuery, FlowQueryExtraction } from '../utils/FlowQueryExtractor';
|
|
14
|
+
import { isAdaptiveCard } from './AdaptiveCardRenderer';
|
|
14
15
|
|
|
15
16
|
// Shared executor instance
|
|
16
17
|
const flowQueryExecutor = new FlowQueryExecutor();
|
|
@@ -222,7 +223,7 @@ export async function processQuery(
|
|
|
222
223
|
export async function* processQueryStream(
|
|
223
224
|
userQuery: string,
|
|
224
225
|
options: FlowQueryAgentOptions
|
|
225
|
-
): AsyncGenerator<{ chunk: string; step: AgentStep['type']; done: boolean; steps?: AgentStep[] }, void, unknown> {
|
|
226
|
+
): AsyncGenerator<{ chunk: string; step: AgentStep['type']; done: boolean; steps?: AgentStep[]; adaptiveCard?: Record<string, unknown> }, void, unknown> {
|
|
226
227
|
const steps: AgentStep[] = [];
|
|
227
228
|
const { systemPrompt, llmOptions = {}, conversationHistory = [], showIntermediateSteps = true } = options;
|
|
228
229
|
|
|
@@ -290,11 +291,15 @@ export async function* processQueryStream(
|
|
|
290
291
|
return;
|
|
291
292
|
}
|
|
292
293
|
|
|
294
|
+
// Check if the result contains an Adaptive Card
|
|
295
|
+
const adaptiveCard = extractAdaptiveCardFromResults(executionResult.results);
|
|
296
|
+
|
|
293
297
|
// Step 4: Stream the interpretation
|
|
294
298
|
const interpretationPrompt = buildInterpretationPrompt(
|
|
295
299
|
userQuery,
|
|
296
300
|
extraction.query!,
|
|
297
|
-
executionResult
|
|
301
|
+
executionResult,
|
|
302
|
+
!!adaptiveCard
|
|
298
303
|
);
|
|
299
304
|
|
|
300
305
|
let interpretationContent = '';
|
|
@@ -317,7 +322,7 @@ export async function* processQueryStream(
|
|
|
317
322
|
timestamp: new Date(),
|
|
318
323
|
});
|
|
319
324
|
|
|
320
|
-
yield { chunk: '', step: 'interpretation', done: true, steps };
|
|
325
|
+
yield { chunk: '', step: 'interpretation', done: true, steps, adaptiveCard };
|
|
321
326
|
} catch (error) {
|
|
322
327
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
323
328
|
yield {
|
|
@@ -329,18 +334,49 @@ export async function* processQueryStream(
|
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Extract an Adaptive Card from the execution results.
|
|
339
|
+
* Checks if any result is an Adaptive Card (type: "AdaptiveCard") and returns it.
|
|
340
|
+
* Searches for Adaptive Cards at the top level or within any property of result objects.
|
|
341
|
+
*/
|
|
342
|
+
function extractAdaptiveCardFromResults(results: unknown[] | undefined): Record<string, unknown> | undefined {
|
|
343
|
+
if (!results || !Array.isArray(results)) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const result of results) {
|
|
348
|
+
// Check if the result itself is an Adaptive Card
|
|
349
|
+
if (isAdaptiveCard(result)) {
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check if any property of the result object is an Adaptive Card
|
|
354
|
+
if (typeof result === 'object' && result !== null) {
|
|
355
|
+
const obj = result as Record<string, unknown>;
|
|
356
|
+
for (const value of Object.values(obj)) {
|
|
357
|
+
if (isAdaptiveCard(value)) {
|
|
358
|
+
return value as Record<string, unknown>;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
|
|
332
367
|
/**
|
|
333
368
|
* Build the prompt for the interpretation phase.
|
|
334
369
|
*/
|
|
335
370
|
function buildInterpretationPrompt(
|
|
336
371
|
originalQuery: string,
|
|
337
372
|
flowQuery: string,
|
|
338
|
-
executionResult: FlowQueryExecutionResult
|
|
373
|
+
executionResult: FlowQueryExecutionResult,
|
|
374
|
+
hasAdaptiveCard: boolean = false
|
|
339
375
|
): string {
|
|
340
376
|
const resultsJson = JSON.stringify(executionResult.results, null, 2);
|
|
341
377
|
const resultCount = executionResult.results?.length || 0;
|
|
342
378
|
|
|
343
|
-
|
|
379
|
+
let prompt = `The user asked: "${originalQuery}"
|
|
344
380
|
|
|
345
381
|
This was translated to the following FlowQuery:
|
|
346
382
|
\`\`\`flowquery
|
|
@@ -353,7 +389,15 @@ The query executed successfully in ${executionResult.executionTime.toFixed(2)}ms
|
|
|
353
389
|
${resultsJson}
|
|
354
390
|
\`\`\`
|
|
355
391
|
|
|
356
|
-
|
|
392
|
+
`;
|
|
393
|
+
|
|
394
|
+
if (hasAdaptiveCard) {
|
|
395
|
+
prompt += `The result is an Adaptive Card that will be rendered automatically in the UI. Please provide a brief introduction or context for the data shown in the card, but do NOT recreate the table or list the data in your response since the card will display it visually.`;
|
|
396
|
+
} else {
|
|
397
|
+
prompt += `Please interpret these results and provide a helpful response to the user's original question.`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return prompt;
|
|
357
401
|
}
|
|
358
402
|
|
|
359
403
|
/**
|
|
@@ -102,3 +102,12 @@
|
|
|
102
102
|
font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
|
|
103
103
|
font-size: 12px;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
.flowquery-adaptive-card-results {
|
|
107
|
+
max-height: 400px;
|
|
108
|
+
overflow: auto;
|
|
109
|
+
border: 1px solid #e0e0e0;
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
padding: 8px;
|
|
112
|
+
background-color: #ffffff;
|
|
113
|
+
}
|
|
@@ -30,10 +30,40 @@ import {
|
|
|
30
30
|
Checkmark24Regular,
|
|
31
31
|
} from '@fluentui/react-icons';
|
|
32
32
|
import { FlowQueryExecutor, FlowQueryExecutionResult } from '../utils/FlowQueryExecutor';
|
|
33
|
+
import { AdaptiveCardRenderer, isAdaptiveCard } from './AdaptiveCardRenderer';
|
|
33
34
|
import './FlowQueryRunner.css';
|
|
34
35
|
|
|
35
36
|
const flowQueryExecutor = new FlowQueryExecutor();
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Extract an Adaptive Card from the execution results.
|
|
40
|
+
* Searches for Adaptive Cards at the top level or within any property of result objects.
|
|
41
|
+
*/
|
|
42
|
+
function extractAdaptiveCardFromResults(results: unknown[] | undefined): Record<string, unknown> | undefined {
|
|
43
|
+
if (!results || !Array.isArray(results)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
// Check if the result itself is an Adaptive Card
|
|
49
|
+
if (isAdaptiveCard(result)) {
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if any property of the result object is an Adaptive Card
|
|
54
|
+
if (typeof result === 'object' && result !== null) {
|
|
55
|
+
const obj = result as Record<string, unknown>;
|
|
56
|
+
for (const value of Object.values(obj)) {
|
|
57
|
+
if (isAdaptiveCard(value)) {
|
|
58
|
+
return value as Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
37
67
|
interface FlowQueryRunnerProps {
|
|
38
68
|
/** Initial query to pre-populate the input */
|
|
39
69
|
initialQuery?: string;
|
|
@@ -192,6 +222,9 @@ export class FlowQueryRunner extends Component<FlowQueryRunnerProps, FlowQueryRu
|
|
|
192
222
|
|
|
193
223
|
const resultCount = result.results?.length || 0;
|
|
194
224
|
const resultsJson = JSON.stringify(result.results, null, 2);
|
|
225
|
+
|
|
226
|
+
// Check if the result contains an Adaptive Card
|
|
227
|
+
const adaptiveCard = extractAdaptiveCardFromResults(result.results);
|
|
195
228
|
|
|
196
229
|
return (
|
|
197
230
|
<div className="flowquery-results-container">
|
|
@@ -214,11 +247,17 @@ export class FlowQueryRunner extends Component<FlowQueryRunnerProps, FlowQueryRu
|
|
|
214
247
|
/>
|
|
215
248
|
</Tooltip>
|
|
216
249
|
</div>
|
|
217
|
-
|
|
218
|
-
<
|
|
219
|
-
{
|
|
220
|
-
</
|
|
221
|
-
|
|
250
|
+
{adaptiveCard ? (
|
|
251
|
+
<div className="flowquery-adaptive-card-results">
|
|
252
|
+
<AdaptiveCardRenderer card={adaptiveCard} />
|
|
253
|
+
</div>
|
|
254
|
+
) : (
|
|
255
|
+
<div className="flowquery-results">
|
|
256
|
+
<pre className="flowquery-results-content">
|
|
257
|
+
{resultsJson}
|
|
258
|
+
</pre>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
222
261
|
</div>
|
|
223
262
|
);
|
|
224
263
|
}
|
|
@@ -7,9 +7,13 @@ export { ApiKeySettings } from './ApiKeySettings';
|
|
|
7
7
|
// FlowQuery Runner
|
|
8
8
|
export { FlowQueryRunner } from './FlowQueryRunner';
|
|
9
9
|
|
|
10
|
+
// Adaptive Card Renderer
|
|
11
|
+
export { AdaptiveCardRenderer, isAdaptiveCard } from './AdaptiveCardRenderer';
|
|
12
|
+
|
|
10
13
|
// FlowQuery Agent
|
|
11
14
|
export { processQuery, processQueryStream } from './FlowQueryAgent';
|
|
12
15
|
export type { AgentStep, AgentResult, FlowQueryAgentOptions, AgentStreamCallback } from './FlowQueryAgent';
|
|
13
16
|
|
|
14
17
|
// Types
|
|
15
18
|
export type { Message } from './ChatMessage';
|
|
19
|
+
export type { AdaptiveCardRendererProps } from './AdaptiveCardRenderer';
|
|
@@ -3,22 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* To add a new plugin:
|
|
5
5
|
* 1. Create a new file in the `loaders/` directory
|
|
6
|
-
* 2. Add the @
|
|
6
|
+
* 2. Add the @FunctionDef decorator with category: 'async' to your loader class
|
|
7
7
|
* 3. Import the class in this file (the decorator auto-registers with FlowQuery)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import FlowQuery from 'flowquery';
|
|
11
11
|
import { FunctionMetadata } from 'flowquery/extensibility';
|
|
12
12
|
|
|
13
|
-
// Import loader classes - the @
|
|
13
|
+
// Import loader classes - the @FunctionDef decorator auto-registers them with FlowQuery
|
|
14
14
|
import './loaders/FetchJson';
|
|
15
15
|
import './loaders/CatFacts';
|
|
16
16
|
import './loaders/MockData';
|
|
17
17
|
import './loaders/Llm';
|
|
18
|
+
import './loaders/Table';
|
|
19
|
+
import './loaders/Form';
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Initialize plugins.
|
|
21
|
-
* Plugins are auto-registered via @
|
|
23
|
+
* Plugins are auto-registered via @FunctionDef decorators when imported.
|
|
22
24
|
* This function just logs the registered plugins for debugging.
|
|
23
25
|
*/
|
|
24
26
|
export function initializePlugins(): void {
|
|
@@ -60,7 +62,7 @@ export async function getAvailableLoaders(): Promise<FunctionMetadata[]> {
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
// Re-export types for external use
|
|
63
|
-
export type { FunctionMetadata, FunctionDefOptions, ParameterSchema, OutputSchema
|
|
65
|
+
export type { FunctionMetadata, FunctionDefOptions, ParameterSchema, OutputSchema } from 'flowquery/extensibility';
|
|
64
66
|
export { FunctionDef } from 'flowquery/extensibility';
|
|
65
67
|
|
|
66
68
|
// Re-export standalone loader functions for use outside of FlowQuery
|
|
@@ -14,9 +14,8 @@ const CAT_FACTS_API = 'https://catfact.ninja/facts';
|
|
|
14
14
|
* CatFacts loader class - fetches random cat facts from the Cat Facts API.
|
|
15
15
|
*/
|
|
16
16
|
@FunctionDef({
|
|
17
|
-
isAsyncProvider: true,
|
|
18
17
|
description: 'Fetches random cat facts from the Cat Facts API (catfact.ninja)',
|
|
19
|
-
category: '
|
|
18
|
+
category: 'async',
|
|
20
19
|
parameters: [
|
|
21
20
|
{
|
|
22
21
|
name: 'count',
|
|
@@ -12,9 +12,8 @@ import { FunctionDef } from 'flowquery/extensibility';
|
|
|
12
12
|
* FetchJson loader class - fetches JSON data from a URL and yields items.
|
|
13
13
|
*/
|
|
14
14
|
@FunctionDef({
|
|
15
|
-
isAsyncProvider: true,
|
|
16
15
|
description: 'Fetches JSON data from a URL. If the response is an array, yields each item individually.',
|
|
17
|
-
category: '
|
|
16
|
+
category: 'async',
|
|
18
17
|
parameters: [
|
|
19
18
|
{
|
|
20
19
|
name: 'url',
|