@audashai/cli 1.0.0

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.
Files changed (49) hide show
  1. package/README.md +57 -0
  2. package/bin/audashai.js +3 -0
  3. package/dist/commands/add.d.ts +1 -0
  4. package/dist/commands/add.js +73 -0
  5. package/dist/commands/init.d.ts +1 -0
  6. package/dist/commands/init.js +62 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +16 -0
  9. package/dist/templates/components/AudashAIChatBot.d.ts +8 -0
  10. package/dist/templates/components/AudashAIChatBot.js +14 -0
  11. package/dist/templates/components/AudashAIInput.d.ts +1 -0
  12. package/dist/templates/components/AudashAIInput.js +114 -0
  13. package/dist/templates/components/AudashAIOutput.d.ts +1 -0
  14. package/dist/templates/components/AudashAIOutput.js +66 -0
  15. package/dist/templates/components/BarChart.d.ts +2 -0
  16. package/dist/templates/components/BarChart.js +41 -0
  17. package/dist/templates/components/ErrorDisplay.d.ts +4 -0
  18. package/dist/templates/components/ErrorDisplay.js +61 -0
  19. package/dist/templates/components/LineChart.d.ts +2 -0
  20. package/dist/templates/components/LineChart.js +41 -0
  21. package/dist/templates/context/AudashAIContext.d.ts +1 -0
  22. package/dist/templates/context/AudashAIContext.js +5 -0
  23. package/dist/templates/context/AudashAIProvider.d.ts +7 -0
  24. package/dist/templates/context/AudashAIProvider.js +50 -0
  25. package/dist/templates/hooks/useAudashAI.d.ts +1 -0
  26. package/dist/templates/hooks/useAudashAI.js +13 -0
  27. package/dist/templates/index.d.ts +9 -0
  28. package/dist/templates/index.js +38 -0
  29. package/dist/templates/types/index.d.ts +39 -0
  30. package/dist/templates/types/index.js +2 -0
  31. package/dist/templates/utils/api.d.ts +3 -0
  32. package/dist/templates/utils/api.js +52 -0
  33. package/dist/utils/config.d.ts +6 -0
  34. package/dist/utils/config.js +15 -0
  35. package/dist/utils/copy-files.d.ts +1 -0
  36. package/dist/utils/copy-files.js +14 -0
  37. package/package.json +42 -0
  38. package/templates/components/AudashAIChatBot.tsx +34 -0
  39. package/templates/components/AudashAIInput.tsx +181 -0
  40. package/templates/components/AudashAIOutput.tsx +90 -0
  41. package/templates/components/BarChart.tsx +113 -0
  42. package/templates/components/ErrorDisplay.tsx +87 -0
  43. package/templates/components/LineChart.tsx +105 -0
  44. package/templates/context/AudashAIContext.tsx +18 -0
  45. package/templates/context/AudashAIProvider.tsx +60 -0
  46. package/templates/hooks/useAudashAI.ts +10 -0
  47. package/templates/index.tsx +12 -0
  48. package/templates/types/index.ts +44 -0
  49. package/templates/utils/api.ts +57 -0
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AudashAIProvider = void 0;
4
+ const react_1 = require("react");
5
+ const api_1 = require("../utils/api");
6
+ const AudashAIContext_1 = require("./AudashAIContext");
7
+ const AudashAIProvider = ({ config, children }) => {
8
+ const [chartData, setChartData] = (0, react_1.useState)(null);
9
+ const [descData, setDescData] = (0, react_1.useState)(null);
10
+ const [loading, setLoading] = (0, react_1.useState)(false);
11
+ const [errorInfo, setErrorInfo] = (0, react_1.useState)(null);
12
+ const [isRefreshing, setIsRefreshing] = (0, react_1.useState)(false);
13
+ const refreshData = async () => {
14
+ if (!config.dataApiUrl || !config.endpoints || config.endpoints.length === 0) {
15
+ console.warn('Data API URL or endpoints not configured');
16
+ return;
17
+ }
18
+ setIsRefreshing(true);
19
+ try {
20
+ const results = await Promise.allSettled(config.endpoints.map((endpoint) => (0, api_1.ingestSingleEndpoint)(endpoint, config)));
21
+ const successful = results.filter((r) => r.status === 'fulfilled').length;
22
+ const failed = results.filter((r) => r.status === 'rejected').length;
23
+ console.log(`✅ Data refresh completed: ${successful} success, ${failed} failed`);
24
+ if (failed > 0) {
25
+ console.warn('Some endpoints failed to ingest');
26
+ }
27
+ }
28
+ catch (error) {
29
+ console.error('Error during data refresh:', error);
30
+ }
31
+ finally {
32
+ setIsRefreshing(false);
33
+ }
34
+ };
35
+ const value = {
36
+ config,
37
+ chartData,
38
+ setChartData,
39
+ descData,
40
+ setDescData,
41
+ loading,
42
+ setLoading,
43
+ errorInfo,
44
+ setErrorInfo,
45
+ refreshData,
46
+ isRefreshing,
47
+ };
48
+ return <AudashAIContext_1.AudashAIContext.Provider value={value}>{children}</AudashAIContext_1.AudashAIContext.Provider>;
49
+ };
50
+ exports.AudashAIProvider = AudashAIProvider;
@@ -0,0 +1 @@
1
+ export declare const useAudashAI: () => any;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAudashAI = void 0;
4
+ const react_1 = require("react");
5
+ const AudashAIContext_1 = require("../context/AudashAIContext");
6
+ const useAudashAI = () => {
7
+ const context = (0, react_1.useContext)(AudashAIContext_1.AudashAIContext);
8
+ if (context === undefined) {
9
+ throw new Error('useAudashAI must be used within AudashAIProvider');
10
+ }
11
+ return context;
12
+ };
13
+ exports.useAudashAI = useAudashAI;
@@ -0,0 +1,9 @@
1
+ export { default as AudashAIChatbot } from './components/AudashAIChatBot';
2
+ export { AudashAIInput } from './components/AudashAIInput';
3
+ export { AudashAIOutput } from './components/AudashAIOutput';
4
+ export { BarChart } from './components/BarChart';
5
+ export { LineChart } from './components/LineChart';
6
+ export { ErrorDisplay } from './components/ErrorDisplay';
7
+ export { AudashAIContext } from './context/AudashAIContext';
8
+ export { useAudashAI } from './hooks/useAudashAI';
9
+ export * from './types';
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.useAudashAI = exports.AudashAIContext = exports.ErrorDisplay = exports.LineChart = exports.BarChart = exports.AudashAIOutput = exports.AudashAIInput = exports.AudashAIChatbot = void 0;
21
+ // src/index.tsx
22
+ var AudashAIChatBot_1 = require("./components/AudashAIChatBot");
23
+ Object.defineProperty(exports, "AudashAIChatbot", { enumerable: true, get: function () { return __importDefault(AudashAIChatBot_1).default; } });
24
+ var AudashAIInput_1 = require("./components/AudashAIInput");
25
+ Object.defineProperty(exports, "AudashAIInput", { enumerable: true, get: function () { return AudashAIInput_1.AudashAIInput; } });
26
+ var AudashAIOutput_1 = require("./components/AudashAIOutput");
27
+ Object.defineProperty(exports, "AudashAIOutput", { enumerable: true, get: function () { return AudashAIOutput_1.AudashAIOutput; } });
28
+ var BarChart_1 = require("./components/BarChart");
29
+ Object.defineProperty(exports, "BarChart", { enumerable: true, get: function () { return BarChart_1.BarChart; } });
30
+ var LineChart_1 = require("./components/LineChart");
31
+ Object.defineProperty(exports, "LineChart", { enumerable: true, get: function () { return LineChart_1.LineChart; } });
32
+ var ErrorDisplay_1 = require("./components/ErrorDisplay");
33
+ Object.defineProperty(exports, "ErrorDisplay", { enumerable: true, get: function () { return ErrorDisplay_1.ErrorDisplay; } });
34
+ var AudashAIContext_1 = require("./context/AudashAIContext");
35
+ Object.defineProperty(exports, "AudashAIContext", { enumerable: true, get: function () { return AudashAIContext_1.AudashAIContext; } });
36
+ var useAudashAI_1 = require("./hooks/useAudashAI");
37
+ Object.defineProperty(exports, "useAudashAI", { enumerable: true, get: function () { return useAudashAI_1.useAudashAI; } });
38
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,39 @@
1
+ export interface ChartDataset {
2
+ label: string;
3
+ data: number[];
4
+ }
5
+ export interface ChartData {
6
+ visualization_type: 'bar' | 'line';
7
+ labels: string[];
8
+ datasets: ChartDataset[];
9
+ }
10
+ export interface ErrorInfo {
11
+ error_type: string;
12
+ message: string;
13
+ reason?: string;
14
+ suggestion?: string;
15
+ available_data_summary?: string;
16
+ documents_found: number;
17
+ }
18
+ export interface QueryResponse {
19
+ response_type: 'visualization' | 'description' | 'error';
20
+ chart_config?: ChartData;
21
+ text_response?: string;
22
+ processing_info?: {
23
+ error_type?: string;
24
+ message?: string;
25
+ suggestion?: string;
26
+ available_data_summary?: string;
27
+ documents_found?: number;
28
+ };
29
+ }
30
+ export interface AudashAIConfig {
31
+ apiKey: string;
32
+ apiUrl: string;
33
+ dataApiUrl?: string;
34
+ endpoints?: string[];
35
+ }
36
+ export interface ChartProps {
37
+ labels: string[];
38
+ datasets: ChartDataset[];
39
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ import { AudashAIConfig } from '../types';
2
+ export declare function fetchDataFromAPI(endpoint: string, baseUrl: string): Promise<unknown>;
3
+ export declare function ingestSingleEndpoint(endpoint: string, config: AudashAIConfig): Promise<unknown>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchDataFromAPI = fetchDataFromAPI;
4
+ exports.ingestSingleEndpoint = ingestSingleEndpoint;
5
+ async function fetchDataFromAPI(endpoint, baseUrl) {
6
+ try {
7
+ const response = await fetch(`${baseUrl}/${endpoint}`, {
8
+ method: 'GET',
9
+ headers: {
10
+ 'Content-Type': 'application/json',
11
+ },
12
+ });
13
+ if (!response.ok) {
14
+ throw new Error(`Failed to fetch data: ${response.status} ${response.statusText}`);
15
+ }
16
+ return await response.json();
17
+ }
18
+ catch (error) {
19
+ console.error('Error fetching data from API:', error);
20
+ throw error;
21
+ }
22
+ }
23
+ async function ingestSingleEndpoint(endpoint, config) {
24
+ if (!config.dataApiUrl) {
25
+ throw new Error('Data API URL not configured');
26
+ }
27
+ let data;
28
+ try {
29
+ data = await fetchDataFromAPI(endpoint, config.dataApiUrl);
30
+ }
31
+ catch (error) {
32
+ console.error(`❌ Failed to fetch data from ${endpoint}:`, error);
33
+ throw new Error(`Failed to fetch data from ${endpoint}`);
34
+ }
35
+ const response = await fetch(`${config.apiUrl}/ingest`, {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ },
40
+ body: JSON.stringify({
41
+ data: data.data || data,
42
+ source_name: endpoint,
43
+ api_key: config.apiKey,
44
+ metadata: { description: `${endpoint} data from API` },
45
+ }),
46
+ });
47
+ if (!response.ok) {
48
+ const errorBody = await response.text();
49
+ throw new Error(`HTTP error! Status: ${response.status}, Endpoint: ${endpoint}, Body: ${errorBody}`);
50
+ }
51
+ return await response.json();
52
+ }
@@ -0,0 +1,6 @@
1
+ interface Config {
2
+ componentsPath: string;
3
+ version: string;
4
+ }
5
+ export declare function getConfig(): Promise<Config | null>;
6
+ export {};
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getConfig = getConfig;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function getConfig() {
10
+ const configPath = path_1.default.join(process.cwd(), 'audashai.config.json');
11
+ if (!(await fs_extra_1.default.pathExists(configPath))) {
12
+ return null;
13
+ }
14
+ return await fs_extra_1.default.readJSON(configPath);
15
+ }
@@ -0,0 +1 @@
1
+ export declare function copyTemplate(templatePath: string, targetPath: string): Promise<void>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.copyTemplate = copyTemplate;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function copyTemplate(templatePath, targetPath) {
10
+ // Path ke templates di root folder (bukan di src)
11
+ const sourcePath = path_1.default.join(__dirname, '../../templates', templatePath);
12
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(targetPath));
13
+ await fs_extra_1.default.copy(sourcePath, targetPath);
14
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@audashai/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to add AudashAI components to your React project",
5
+ "bin": {
6
+ "audashai": "./bin/audashai.js"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "bin",
11
+ "templates",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "cli",
21
+ "audashai",
22
+ "react",
23
+ "components"
24
+ ],
25
+ "author": "AudashAI Team",
26
+ "license": "MIT",
27
+ "type": "commonjs",
28
+ "dependencies": {
29
+ "chalk": "^5.6.2",
30
+ "commander": "^14.0.2",
31
+ "execa": "^9.6.0",
32
+ "fs-extra": "^11.3.2",
33
+ "inquirer": "^13.0.1",
34
+ "ora": "^9.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/fs-extra": "^11.0.4",
38
+ "@types/inquirer": "^9.0.9",
39
+ "@types/node": "^24.10.1",
40
+ "typescript": "^5.9.3"
41
+ }
42
+ }
@@ -0,0 +1,34 @@
1
+ import { AudashAIProvider } from '../context/AudashAIProvider';
2
+ import { AudashAIInput } from './AudashAIInput';
3
+ import { AudashAIOutput } from './AudashAIOutput';
4
+
5
+ interface AudashAIChatbotProps {
6
+ apiKey: string;
7
+ apiUrl: string;
8
+ dataApiUrl?: string;
9
+ endpoints?: string[];
10
+ }
11
+
12
+ const AudashAIChatbot: React.FC<AudashAIChatbotProps> = ({
13
+ apiKey,
14
+ apiUrl,
15
+ dataApiUrl,
16
+ endpoints,
17
+ }) => {
18
+ return (
19
+ <AudashAIProvider config={{ apiKey, apiUrl, dataApiUrl, endpoints }}>
20
+ <div className="w-full max-w-7xl mx-auto p-4">
21
+ <div className="flex flex-col lg:flex-row-reverse gap-4 lg:h-[300px] items-stretch">
22
+ <div className="w-full lg:w-[350px] flex-shrink-0">
23
+ <AudashAIInput />
24
+ </div>
25
+ <div className="w-full flex-1 min-w-0 overflow-hidden">
26
+ <AudashAIOutput />
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </AudashAIProvider>
31
+ );
32
+ };
33
+
34
+ export default AudashAIChatbot;
@@ -0,0 +1,181 @@
1
+ import { useState } from 'react';
2
+ import type { QueryResponse } from '../types';
3
+ import { useAudashAI } from '../hooks/useAudashAI';
4
+
5
+ export const AudashAIInput: React.FC = () => {
6
+ const {
7
+ config,
8
+ setChartData,
9
+ setDescData,
10
+ setLoading,
11
+ setErrorInfo,
12
+ loading,
13
+ refreshData,
14
+ isRefreshing,
15
+ } = useAudashAI();
16
+ const [inputValue, setInputValue] = useState('');
17
+ const [error, setError] = useState('');
18
+
19
+ const handleSubmit = async () => {
20
+ const wordCount = inputValue.trim().split(/\s+/).length;
21
+ if (inputValue.trim() === '' || wordCount < 3) {
22
+ setError('Minimal 3 kata.');
23
+ return;
24
+ }
25
+
26
+ setLoading(true);
27
+ setChartData(null);
28
+ setDescData(null);
29
+ setErrorInfo(null);
30
+
31
+ try {
32
+ const response = await fetch(`${config.apiUrl}/query`, {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify({
38
+ query: inputValue,
39
+ api_key: config.apiKey,
40
+ limit: 10,
41
+ }),
42
+ });
43
+
44
+ const result: QueryResponse = await response.json();
45
+
46
+ if (result.response_type === 'visualization') {
47
+ setChartData(result.chart_config || null);
48
+ setDescData(null);
49
+ setErrorInfo(null);
50
+ } else if (result.response_type === 'description') {
51
+ setDescData(result.text_response || null);
52
+ setChartData(null);
53
+ setErrorInfo(null);
54
+ } else if (result.response_type === 'error') {
55
+ setChartData(null);
56
+ setDescData(null);
57
+ setErrorInfo({
58
+ error_type: result.processing_info?.error_type || 'unknown_error',
59
+ message: result.text_response || 'Terjadi kesalahan',
60
+ reason: result.processing_info?.message,
61
+ suggestion: result.processing_info?.suggestion,
62
+ available_data_summary: result.processing_info?.available_data_summary,
63
+ documents_found: result.processing_info?.documents_found || 0,
64
+ });
65
+ }
66
+ } catch (error: unknown) {
67
+ console.error('Error:', error);
68
+ const reason = error instanceof Error ? error.message : 'Network error';
69
+
70
+ setChartData(null);
71
+ setDescData(null);
72
+ setErrorInfo({
73
+ error_type: 'network_error',
74
+ message: 'Koneksi gagal',
75
+ reason,
76
+ suggestion: 'Coba lagi nanti',
77
+ documents_found: 0,
78
+ });
79
+ } finally {
80
+ setLoading(false);
81
+ }
82
+ };
83
+
84
+ const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
85
+ setInputValue(event.target.value);
86
+ if (error) setError('');
87
+ };
88
+
89
+ const handleKeyPress = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
90
+ if (event.key === 'Enter' && !event.shiftKey) {
91
+ event.preventDefault();
92
+ handleSubmit();
93
+ }
94
+ };
95
+
96
+ return (
97
+ <div className="bg-white h-full w-full rounded-xl border border-slate-200 shadow-sm flex flex-col">
98
+ <div className="px-4 py-3 border-b border-slate-100 flex justify-between items-center bg-slate-50/50 rounded-t-xl">
99
+ <div className="flex items-center gap-2">
100
+ <div className="h-2 w-2 rounded-full bg-emerald-500"></div>
101
+ <h1 className="text-xs font-bold text-slate-600 uppercase tracking-wider">AI Control</h1>
102
+ </div>
103
+
104
+ {config.dataApiUrl && (
105
+ <button
106
+ onClick={refreshData}
107
+ disabled={isRefreshing}
108
+ className="hover:cursor-pointer text-xs text-slate-400 hover:text-indigo-600 transition-colors disabled:opacity-50"
109
+ title="Refresh Data Source"
110
+ >
111
+ <svg
112
+ className={`h-4 w-4 ${isRefreshing ? 'animate-spin text-indigo-500' : ''}`}
113
+ fill="none"
114
+ viewBox="0 0 24 24"
115
+ stroke="currentColor"
116
+ strokeWidth="2"
117
+ >
118
+ <path
119
+ strokeLinecap="round"
120
+ strokeLinejoin="round"
121
+ d="M4 4v5h.058M20 20v-5h-.058M4.05 9a9 9 0 0115.66-4.5M19.95 15a9 9 0 01-15.66 4.5"
122
+ />
123
+ </svg>
124
+ </button>
125
+ )}
126
+ </div>
127
+
128
+ <div className="p-4 flex flex-col flex-1 gap-3">
129
+ <div
130
+ className={`relative flex-1 rounded-xl border transition-all duration-200 flex flex-col ${
131
+ error
132
+ ? 'border-red-300 bg-red-50/30'
133
+ : 'border-slate-200 bg-slate-50/30 focus-within:bg-white focus-within:ring-2 focus-within:ring-indigo-100 focus-within:border-indigo-400'
134
+ }`}
135
+ >
136
+ <textarea
137
+ value={inputValue}
138
+ onChange={handleInputChange}
139
+ onKeyPress={handleKeyPress}
140
+ className="w-full h-full bg-transparent px-4 py-3 text-sm text-slate-700 placeholder:text-slate-400 focus:outline-none resize-none"
141
+ placeholder="Ketik perintah analisis..."
142
+ />
143
+ </div>
144
+
145
+ {error && <p className="text-xs text-red-500 font-medium px-1">{error}</p>}
146
+
147
+ <button
148
+ onClick={handleSubmit}
149
+ disabled={loading || !inputValue.trim()}
150
+ className="w-full flex justify-center items-center gap-2 bg-indigo-600 hover:bg-indigo-700 disabled:bg-slate-300 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition-all shadow-sm"
151
+ >
152
+ {loading ? (
153
+ <span className="flex items-center gap-2">
154
+ <svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
155
+ <circle
156
+ className="opacity-25"
157
+ cx="12"
158
+ cy="12"
159
+ r="10"
160
+ stroke="currentColor"
161
+ strokeWidth="4"
162
+ fill="none"
163
+ ></circle>
164
+ <path
165
+ className="opacity-75"
166
+ fill="currentColor"
167
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
168
+ ></path>
169
+ </svg>
170
+ Processing
171
+ </span>
172
+ ) : (
173
+ 'Analisis Data'
174
+ )}
175
+ </button>
176
+
177
+ <p className="text-center text-[10px] text-slate-400">AudashAI Analyst v1.0</p>
178
+ </div>
179
+ </div>
180
+ );
181
+ };
@@ -0,0 +1,90 @@
1
+ import { useAudashAI } from '../hooks/useAudashAI';
2
+ import { BarChart } from './BarChart';
3
+ import { ErrorDisplay } from './ErrorDisplay';
4
+ import { LineChart } from './LineChart';
5
+
6
+ export const AudashAIOutput: React.FC = () => {
7
+ const { chartData, descData, loading, errorInfo } = useAudashAI();
8
+
9
+ const renderContent = () => {
10
+ if (loading) {
11
+ return (
12
+ <div className="w-full h-full flex flex-col justify-center items-center p-8 space-y-6">
13
+ <div className="w-full h-48 bg-slate-100 rounded-lg animate-pulse relative overflow-hidden">
14
+ <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent -translate-x-full animate-[shimmer_1.5s_infinite]"></div>
15
+ </div>
16
+ <div className="w-full space-y-2">
17
+ <div className="h-3 bg-slate-100 rounded w-1/2 animate-pulse"></div>
18
+ <div className="h-3 bg-slate-100 rounded w-1/3 animate-pulse"></div>
19
+ </div>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ if (errorInfo) {
25
+ return (
26
+ <div className="p-4 h-full flex flex-col justify-center">
27
+ <ErrorDisplay errorInfo={errorInfo} />
28
+ </div>
29
+ );
30
+ }
31
+
32
+ if (descData) {
33
+ return (
34
+ <div className="w-full h-full overflow-y-auto p-6 scrollbar-thin scrollbar-thumb-slate-200">
35
+ <div className="prose prose-sm max-w-none">
36
+ <h3 className="text-slate-800 font-semibold mb-3 flex items-center gap-2">
37
+ <span className="bg-indigo-100 p-1 rounded">✨</span> Hasil Analisis
38
+ </h3>
39
+ <p className="text-slate-600 leading-relaxed whitespace-pre-wrap">{descData}</p>
40
+ </div>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ if (chartData) {
46
+ return (
47
+ <div className="w-full h-full flex flex-col p-4 animate-in zoom-in-95 duration-300">
48
+ <div className="flex-1 relative min-h-[300px]">
49
+ {chartData.visualization_type === 'bar' && (
50
+ <BarChart labels={chartData.labels} datasets={chartData.datasets} />
51
+ )}
52
+ {chartData.visualization_type === 'line' && (
53
+ <LineChart labels={chartData.labels} datasets={chartData.datasets} />
54
+ )}
55
+ </div>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ return (
61
+ <div className="h-full flex flex-col items-center justify-center text-center p-8 bg-slate-50/30">
62
+ <div className="w-16 h-16 bg-white rounded-full shadow-sm border border-slate-100 flex items-center justify-center mb-4">
63
+ <svg
64
+ className="h-8 w-8 text-slate-300"
65
+ fill="none"
66
+ viewBox="0 0 24 24"
67
+ stroke="currentColor"
68
+ >
69
+ <path
70
+ strokeLinecap="round"
71
+ strokeLinejoin="round"
72
+ strokeWidth={1}
73
+ d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
74
+ />
75
+ </svg>
76
+ </div>
77
+ <h3 className="text-sm font-semibold text-slate-700">Area Visualisasi</h3>
78
+ <p className="text-xs text-slate-400 mt-1 max-w-[200px]">
79
+ Grafik akan muncul di sini setelah Anda mengirimkan perintah.
80
+ </p>
81
+ </div>
82
+ );
83
+ };
84
+
85
+ return (
86
+ <div className="bg-white h-full w-full rounded-xl border border-slate-200 shadow-sm overflow-hidden flex flex-col">
87
+ {renderContent()}
88
+ </div>
89
+ );
90
+ };