kenken999 commited on
Commit
69996c8
·
verified ·
1 Parent(s): 07a008d

Upload folder using huggingface_hub

Browse files
Files changed (11) hide show
  1. .htaccess +13 -0
  2. LICENSE +21 -0
  3. README.md +228 -12
  4. api-functions.js +540 -0
  5. app.py +323 -0
  6. config.json +58 -0
  7. exports/.gitkeep +0 -0
  8. index.html +442 -0
  9. requirements.txt +1 -0
  10. supabase-config.js +149 -0
  11. workflows/.gitkeep +0 -0
.htaccess ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Disable Laravel routing for this directory
2
+ RewriteEngine Off
3
+
4
+ # Allow directory browsing (optional)
5
+ Options +Indexes
6
+
7
+ # Set index file
8
+ DirectoryIndex index.html
9
+
10
+ # Set MIME types
11
+ AddType text/html .html
12
+ AddType text/css .css
13
+ AddType application/javascript .js
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 API Workflow Builder Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,228 @@
1
- ---
2
- title: Api Workflow Builder
3
- emoji: 🌖
4
- colorFrom: indigo
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 6.6.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 API Workflow Builder
2
+
3
+ Visual page builder with API integration powered by GrapeJS and Supabase.
4
+
5
+ ## 📋 概要
6
+
7
+ ドラッグ&ドロップでAPIワークフロー画面を構築できるビジュアルエディタ。
8
+ Shop11 APIと連携し、商品・顧客・査定データを簡単に操作できます。
9
+
10
+ ## ✨ 機能
11
+
12
+ ### 📦 ページビルダー
13
+ - **ドラッグ&ドロップ**: GrapeJSベースの直感的なエディタ
14
+ - **リアルタイムプレビュー**: 編集内容をその場で確認
15
+ - **Supabase連携**: ページデータをクラウドに保存
16
+ - **HTML出力**: 作成したページをHTMLファイルとしてダウンロード
17
+
18
+ ### 🔌 APIブロック
19
+
20
+ #### Shop11 API連携ブロック
21
+ 1. **地金チェック削除** (`gold-check-delete`)
22
+ - エンドポイント: `POST /api/shouhin/gold_check_id_delete`
23
+ - 商品IDを指定して地金チェックを削除
24
+
25
+ 2. **査定タイトル生成** (`satei-title`)
26
+ - エンドポイント: `GET /api/satei_func/create_title/{product_id}`
27
+ - 商品IDから査定タイトルを自動生成
28
+
29
+ 3. **メール送信** (`send-notification`)
30
+ - エンドポイント: `POST /api/notification/mail`
31
+ - 宛先・件名・本文を指定してメール送信
32
+
33
+ 4. **商品検索** (`product-search`)
34
+ - エンドポイント: `GET /api/auto_complete_refasta/ResultSearch`
35
+ - キーワードで商品を検索
36
+
37
+ 5. **商品データテーブル** (`product-data-table`)
38
+ - 商品データを検索してテーブル表示
39
+ - ソート・フィルタリング対応
40
+
41
+ 6. **顧客検索テーブル** (`customer-data-table`)
42
+ - 顧客名・電話番号で検索
43
+ - 顧客情報を一覧表示
44
+
45
+ 7. **査定検索テーブル** (`satei-data-table`)
46
+ - 査定データを検索・表示
47
+ - ステータス別バッジ表示
48
+
49
+ 8. **商品登録フォーム** (`product-register-form`)
50
+ - エンドポイント: `POST /api/shouhin/register`
51
+ - 新規商品をフォームから登録
52
+
53
+ ### 💾 データ永続化
54
+ - **Supabase Tables**:
55
+ - `page_builder_pages`: ページデータ保存
56
+ - `api_execution_logs`: API実行履歴ログ
57
+ - **LocalStorage**: エディタの自動保存
58
+
59
+ ## 🚀 使い方
60
+
61
+ ### 1. アクセス
62
+ ```
63
+ http://localhost/shop11/public/page-builder/
64
+ ```
65
+
66
+ ### 2. ブロックを追加
67
+ 1. 左サイドバーから「Shop11 API」カテゴリを選択
68
+ 2. 使いたいブロックをキャンバスにドラッグ&ドロップ
69
+ 3. ブロック内の入力フォームに値を入力
70
+ 4. ボタンをクリックしてAPI実行
71
+
72
+ ### 3. ページを保存
73
+ 1. ヘッダーの「💾 保存」ボタンをクリック
74
+ 2. ページ名を入力
75
+ 3. Supabaseに保存 + HTMLファイルをダウンロード
76
+
77
+ ### 4. ページを読込
78
+ 1. ヘッダーの「📂 読込」ボタンをクリック
79
+ 2. 過去に保存したページ一覧から選択
80
+ 3. エディタに読み込まれます
81
+
82
+ ### 5. プレビュー
83
+ - 「👁️ プレビュー」ボタンで新しいタブで表示確認
84
+
85
+ ## 📁 ファイル構成
86
+
87
+ ```
88
+ page-builder/
89
+ ├── index.html # メインエディタ画面
90
+ ├── supabase-config.js # Supabase接続設定
91
+ ├── api-functions.js # API呼び出し関数
92
+ ├── .htaccess # Apache設定
93
+ └── README.md # このファイル
94
+ ```
95
+
96
+ ## 🔧 技術スタック
97
+
98
+ - **フロントエンド**: GrapeJS, Bootstrap 4.5.2, jQuery 3.5.1
99
+ - **バックエンド**: Laravel (Shop11), Supabase
100
+ - **データベース**: PostgreSQL (Supabase)
101
+ - **ストレージ**: Supabase Storage (将来実装予定)
102
+
103
+ ## 🗄️ データベーススキーマ
104
+
105
+ ### page_builder_pages
106
+ ```sql
107
+ CREATE TABLE page_builder_pages (
108
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
109
+ name text NOT NULL,
110
+ html_content text,
111
+ css_content text,
112
+ components_json jsonb,
113
+ created_at timestamp with time zone DEFAULT now(),
114
+ updated_at timestamp with time zone DEFAULT now()
115
+ );
116
+ ```
117
+
118
+ ### api_execution_logs
119
+ ```sql
120
+ CREATE TABLE api_execution_logs (
121
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
122
+ api_name text NOT NULL,
123
+ endpoint text NOT NULL,
124
+ request_data jsonb,
125
+ response_data jsonb,
126
+ status text CHECK (status IN ('success', 'error')),
127
+ executed_at timestamp with time zone DEFAULT now()
128
+ );
129
+ ```
130
+
131
+ ## 🔐 Supabase設定
132
+
133
+ ### 環境変数
134
+ - **SUPABASE_URL**: `https://rootomzbucovwdqsscqd.supabase.co`
135
+ - **SUPABASE_ANON_KEY**: `eyJhbGci...` (supabase-config.jsに記載)
136
+
137
+ ### RLS (Row Level Security)
138
+ 現在はテスト用に全アクセス許可。本番環境では認証を追加してください。
139
+
140
+ ## 🎨 カスタマイズ
141
+
142
+ ### 新しいAPIブロックを追加
143
+
144
+ 1. **index.htmlに登録**:
145
+ ```javascript
146
+ blockManager.add('my-custom-block', {
147
+ label: '新しいブロック',
148
+ category: 'Shop11 API',
149
+ content: '<div class="shop11-api-block">...</div>'
150
+ });
151
+ ```
152
+
153
+ 2. **api-functions.jsに関数追加**:
154
+ ```javascript
155
+ function myCustomFunction(button) {
156
+ const block = button.closest('.shop11-api-block');
157
+ // API呼び出し処理
158
+ fetch('/api/my-endpoint', {...})
159
+ .then(res => res.json())
160
+ .then(data => {
161
+ // Supabaseログ保存
162
+ if (window.saveApiLogToSupabase) {
163
+ saveApiLogToSupabase('API名', endpoint, requestData, data, 'success');
164
+ }
165
+ });
166
+ }
167
+ ```
168
+
169
+ 3. **イベントリスナーに登録**:
170
+ ```javascript
171
+ case 'myCustomAction':
172
+ myCustomFunction(e.target);
173
+ break;
174
+ ```
175
+
176
+ ## 📊 API実行ログの確認
177
+
178
+ ```javascript
179
+ // 直近50件のログを取得
180
+ getApiLogsFromSupabase(50).then(result => {
181
+ console.log(result.data);
182
+ });
183
+ ```
184
+
185
+ または、Supabase Dashboardで直接確認:
186
+ ```sql
187
+ SELECT * FROM api_execution_logs
188
+ ORDER BY executed_at DESC
189
+ LIMIT 50;
190
+ ```
191
+
192
+ ## 🐛 トラブルシューティング
193
+
194
+ ### ブロックがドラッグできない
195
+ - ブラウザのキャッシュをクリア
196
+ - F5でページをリロード
197
+
198
+ ### APIエラーが発生する
199
+ - CSRF Tokenが正しく設定されているか確認
200
+ - Laravel側のルーティングを確認
201
+ - ネットワークタブでHTTPステータスコードを確認
202
+
203
+ ### Supabaseに保存できない
204
+ - Supabase接続情報が正しいか確認
205
+ - RLSポリシーでアクセスが許可されているか確認
206
+ - ブラウザコンソールでエラーメッセージを確認
207
+
208
+ ## 🚧 今後の拡張予定
209
+
210
+ - [ ] Supabase Storageへのファイルアップロード
211
+ - [ ] 認証機能の追加
212
+ - [ ] ページバージョン管理
213
+ - [ ] コラボレーション機能
214
+ - [ ] カスタムコンポーネント作成UI
215
+ - [ ] ワークフロー自動化
216
+ - [ ] Hugging Face Spaceへのデプロイ
217
+
218
+ ## 📝 ライセンス
219
+
220
+ MIT License (予定)
221
+
222
+ ## 👤 作者
223
+
224
+ Created for API workflow automation and visual page building.
225
+
226
+ ## 📞 サポート
227
+
228
+ 問題が発生した場合は、ブラウザのコンソールログを確認してください。
api-functions.js ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Shop11 API Functions
2
+ // ページビルダーで作成したページで使用するAPI呼び出し関数
3
+
4
+ document.addEventListener('DOMContentLoaded', function() {
5
+ console.log('✅ Shop11 API Functions loaded');
6
+
7
+ // すべてのAPIボタンにイベントリスナーを追加
8
+ document.addEventListener('click', function(e) {
9
+ const action = e.target.getAttribute('data-action');
10
+ if (!action) return;
11
+
12
+ switch(action) {
13
+ case 'goldCheckDelete':
14
+ goldCheckDelete(e.target);
15
+ break;
16
+ case 'createSateiTitle':
17
+ createSateiTitle(e.target);
18
+ break;
19
+ case 'sendNotification':
20
+ sendNotification(e.target);
21
+ break;
22
+ case 'searchProduct':
23
+ searchProduct(e.target);
24
+ break;
25
+ case 'loadProductTable':
26
+ loadProductTable(e.target);
27
+ break;
28
+ case 'clearProductTable':
29
+ clearProductTable(e.target);
30
+ break;
31
+ case 'loadCustomerTable':
32
+ loadCustomerTable(e.target);
33
+ break;
34
+ case 'clearCustomerTable':
35
+ clearCustomerTable(e.target);
36
+ break;
37
+ case 'loadSateiTable':
38
+ loadSateiTable(e.target);
39
+ break;
40
+ case 'clearSateiTable':
41
+ clearSateiTable(e.target);
42
+ break;
43
+ case 'registerProduct':
44
+ registerProduct(e.target);
45
+ break;
46
+ }
47
+ });
48
+ });
49
+
50
+ // 地金チェック削除
51
+ function goldCheckDelete(button) {
52
+ const block = button.closest('.shop11-api-block');
53
+ const productId = block.querySelector('.gold-product-id').value;
54
+ const resultDiv = block.querySelector('.gold-check-result');
55
+
56
+ if (!productId) {
57
+ alert('商品IDを入力してください');
58
+ return;
59
+ }
60
+
61
+ resultDiv.style.display = 'block';
62
+ resultDiv.innerHTML = '処理中...';
63
+
64
+ const requestData = { product_id: productId };
65
+
66
+ fetch('/api/shouhin/gold_check_id_delete', {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
71
+ },
72
+ body: JSON.stringify(requestData)
73
+ })
74
+ .then(res => res.json())
75
+ .then(data => {
76
+ resultDiv.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
77
+
78
+ // Supabaseにログ保存
79
+ if (window.saveApiLogToSupabase) {
80
+ saveApiLogToSupabase(
81
+ '地金チェック削除',
82
+ '/api/shouhin/gold_check_id_delete',
83
+ requestData,
84
+ data,
85
+ 'success'
86
+ );
87
+ }
88
+ })
89
+ .catch(err => {
90
+ resultDiv.innerHTML = '<span style="color:red;">エラー: ' + err.message + '</span>';
91
+
92
+ // Supabaseにエラーログ保存
93
+ if (window.saveApiLogToSupabase) {
94
+ saveApiLogToSupabase(
95
+ '地金チェック削除',
96
+ '/api/shouhin/gold_check_id_delete',
97
+ requestData,
98
+ { error: err.message },
99
+ 'error'
100
+ );
101
+ }
102
+ });
103
+ }
104
+
105
+ // 査定タイトル生成
106
+ function createSateiTitle(button) {
107
+ const block = button.closest('.shop11-api-block');
108
+ const productId = block.querySelector('.satei-product-id').value;
109
+ const resultDiv = block.querySelector('.satei-title-result');
110
+
111
+ if (!productId) {
112
+ alert('商品IDを入力してください');
113
+ return;
114
+ }
115
+
116
+ resultDiv.style.display = 'block';
117
+ resultDiv.innerHTML = '生成中...';
118
+
119
+ const endpoint = '/api/satei_func/create_title/' + productId;
120
+ const requestData = { product_id: productId };
121
+
122
+ fetch(endpoint, {
123
+ method: 'GET',
124
+ headers: {
125
+ 'Content-Type': 'application/json'
126
+ }
127
+ })
128
+ .then(res => res.json())
129
+ .then(data => {
130
+ resultDiv.innerHTML = '<strong>タイトル:</strong><br>' + (data.title || JSON.stringify(data, null, 2));
131
+
132
+ if (window.saveApiLogToSupabase) {
133
+ saveApiLogToSupabase('査定タイトル生成', endpoint, requestData, data, 'success');
134
+ }
135
+ })
136
+ .catch(err => {
137
+ resultDiv.innerHTML = '<span style="color:red;">エラー: ' + err.message + '</span>';
138
+
139
+ if (window.saveApiLogToSupabase) {
140
+ saveApiLogToSupabase('査定タイトル生成', endpoint, requestData, { error: err.message }, 'error');
141
+ }
142
+ });
143
+ }
144
+
145
+ // メール送信
146
+ function sendNotification(button) {
147
+ const block = button.closest('.shop11-api-block');
148
+ const to = block.querySelector('.mail-to').value;
149
+ const subject = block.querySelector('.mail-subject').value;
150
+ const body = block.querySelector('.mail-body').value;
151
+ const resultDiv = block.querySelector('.mail-result');
152
+
153
+ if (!to || !subject || !body) {
154
+ alert('すべての項目を入力してください');
155
+ return;
156
+ }
157
+
158
+ resultDiv.style.display = 'block';
159
+ resultDiv.innerHTML = '送信中...';
160
+
161
+ const endpoint = '/api/notification/mail';
162
+ const requestData = { to, subject, body };
163
+
164
+ fetch(endpoint, {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Content-Type': 'application/json',
168
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
169
+ },
170
+ body: JSON.stringify(requestData)
171
+ })
172
+ .then(res => res.json())
173
+ .then(data => {
174
+ resultDiv.innerHTML = '<span style="color:green;">✅ 送信完了</span><br><pre>' + JSON.stringify(data, null, 2) + '</pre>';
175
+
176
+ if (window.saveApiLogToSupabase) {
177
+ saveApiLogToSupabase('メール送信', endpoint, requestData, data, 'success');
178
+ }
179
+ })
180
+ .catch(err => {
181
+ resultDiv.innerHTML = '<span style="color:red;">❌ エラー: ' + err.message + '</span>';
182
+
183
+ if (window.saveApiLogToSupabase) {
184
+ saveApiLogToSupabase('メール送信', endpoint, requestData, { error: err.message }, 'error');
185
+ }
186
+ });
187
+ }
188
+
189
+ // 商品検索
190
+ function searchProduct(button) {
191
+ const block = button.closest('.shop11-api-block');
192
+ const keyword = block.querySelector('.search-keyword').value;
193
+ const resultDiv = block.querySelector('.search-result');
194
+
195
+ if (!keyword) {
196
+ alert('検索キーワードを入力してください');
197
+ return;
198
+ }
199
+
200
+ resultDiv.style.display = 'block';
201
+ resultDiv.innerHTML = '検索中...';
202
+
203
+ const endpoint = '/api/auto_complete_refasta/ResultSearch?q=' + encodeURIComponent(keyword);
204
+ const requestData = { keyword };
205
+
206
+ fetch(endpoint, {
207
+ method: 'GET',
208
+ headers: {
209
+ 'Content-Type': 'application/json'
210
+ }
211
+ })
212
+ .then(res => res.json())
213
+ .then(data => {
214
+ if (Array.isArray(data) && data.length > 0) {
215
+ let html = '<ul style="list-style:none; padding:0;">';
216
+ data.forEach(item => {
217
+ html += '<li style="padding:10px; border-bottom:1px solid #eee;">' +
218
+ (item.label || item.name || JSON.stringify(item)) + '</li>';
219
+ });
220
+ html += '</ul>';
221
+ resultDiv.innerHTML = html;
222
+ } else {
223
+ resultDiv.innerHTML = '検索結果なし';
224
+ }
225
+
226
+ if (window.saveApiLogToSupabase) {
227
+ saveApiLogToSupabase('商品検索', endpoint, requestData, data, 'success');
228
+ }
229
+ })
230
+ .catch(err => {
231
+ resultDiv.innerHTML = '<span style="color:red;">エラー: ' + err.message + '</span>';
232
+
233
+ if (window.saveApiLogToSupabase) {
234
+ saveApiLogToSupabase('商品検索', endpoint, requestData, { error: err.message }, 'error');
235
+ }
236
+ });
237
+ }
238
+
239
+ // 商品データテーブル読込
240
+ function loadProductTable(button) {
241
+ const block = button.closest('.shop11-api-block');
242
+ const keyword = block.querySelector('.product-search-keyword').value;
243
+ const tableBody = block.querySelector('.product-table-body');
244
+ const infoDiv = block.querySelector('.product-table-info');
245
+
246
+ if (!keyword) {
247
+ alert('検索キーワードを入力してください');
248
+ return;
249
+ }
250
+
251
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center">🔍 検索中...</td></tr>';
252
+
253
+ const endpoint = '/api/auto_complete_refasta/ResultSearch?q=' + encodeURIComponent(keyword);
254
+ const requestData = { keyword };
255
+
256
+ fetch(endpoint, {
257
+ method: 'GET',
258
+ headers: {
259
+ 'Content-Type': 'application/json'
260
+ }
261
+ })
262
+ .then(res => res.json())
263
+ .then(data => {
264
+ if (Array.isArray(data) && data.length > 0) {
265
+ let html = '';
266
+ data.forEach((item, index) => {
267
+ html += '<tr>';
268
+ html += '<td>' + (item.product_id || item.id || index + 1) + '</td>';
269
+ html += '<td>' + (item.label || item.name || item.title || '-') + '</td>';
270
+ html += '<td>' + (item.brand || item.maker || '-') + '</td>';
271
+ html += '<td>' + (item.category || item.category_name || '-') + '</td>';
272
+ html += '<td class="text-right">' + (item.price ? '¥' + parseInt(item.price).toLocaleString() : '-') + '</td>';
273
+ html += '<td>' + getStatusBadge(item.status) + '</td>';
274
+ html += '<td>' +
275
+ '<button class="btn btn-sm btn-info mr-1" onclick="alert(\'商品ID: ' + (item.product_id || item.id) + '\')">📝 詳細</button>' +
276
+ '<button class="btn btn-sm btn-warning" onclick="alert(\'編集機能は準備中です\')">✏️ 編集</button>' +
277
+ '</td>';
278
+ html += '</tr>';
279
+ });
280
+ tableBody.innerHTML = html;
281
+ infoDiv.innerHTML = '<small>✅ ' + data.length + '件の商品が見つかりました</small>';
282
+
283
+ if (window.saveApiLogToSupabase) {
284
+ saveApiLogToSupabase('商品データテーブル読込', endpoint, requestData, { count: data.length }, 'success');
285
+ }
286
+ } else {
287
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">📭 検索結果なし</td></tr>';
288
+ infoDiv.innerHTML = '<small>検索結果: 0件</small>';
289
+
290
+ if (window.saveApiLogToSupabase) {
291
+ saveApiLogToSupabase('商品データテーブル読込', endpoint, requestData, { count: 0 }, 'success');
292
+ }
293
+ }
294
+ })
295
+ .catch(err => {
296
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-danger">❌ エラー: ' + err.message + '</td></tr>';
297
+ infoDiv.innerHTML = '<small class="text-danger">データ読込エラー</small>';
298
+
299
+ if (window.saveApiLogToSupabase) {
300
+ saveApiLogToSupabase('商品データテーブル読込', endpoint, requestData, { error: err.message }, 'error');
301
+ }
302
+ });
303
+ }
304
+
305
+ // 商品テーブルクリア
306
+ function clearProductTable(button) {
307
+ const block = button.closest('.shop11-api-block');
308
+ const keyword = block.querySelector('.product-search-keyword');
309
+ const tableBody = block.querySelector('.product-table-body');
310
+ const infoDiv = block.querySelector('.product-table-info');
311
+
312
+ keyword.value = '';
313
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr>';
314
+ infoDiv.innerHTML = '';
315
+ }
316
+
317
+ // ステータスバッジ生成
318
+ function getStatusBadge(status) {
319
+ if (!status) return '<span class="badge badge-secondary">-</span>';
320
+
321
+ const statusMap = {
322
+ '1': { label: '在庫あり', color: 'success' },
323
+ '2': { label: '予約中', color: 'warning' },
324
+ '3': { label: '売約済', color: 'danger' },
325
+ '4': { label: '出品中', color: 'info' },
326
+ '5': { label: '取り下げ', color: 'secondary' }
327
+ };
328
+
329
+ const statusInfo = statusMap[status] || { label: status, color: 'secondary' };
330
+ return '<span class="badge badge-' + statusInfo.color + '">' + statusInfo.label + '</span>';
331
+ }
332
+
333
+ // 顧客データテーブル読込
334
+ function loadCustomerTable(button) {
335
+ const block = button.closest('.shop11-api-block');
336
+ const keyword = block.querySelector('.customer-search-keyword').value;
337
+ const tableBody = block.querySelector('.customer-table-body');
338
+ const infoDiv = block.querySelector('.customer-table-info');
339
+
340
+ if (!keyword) {
341
+ alert('検索キーワードを入力してください');
342
+ return;
343
+ }
344
+
345
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center">🔍 検索中...</td></tr>';
346
+
347
+ const endpoint = '/api/Eoc/search?keyword=' + encodeURIComponent(keyword);
348
+ const requestData = { keyword };
349
+
350
+ fetch(endpoint, {
351
+ method: 'GET',
352
+ headers: { 'Content-Type': 'application/json' }
353
+ })
354
+ .then(res => res.json())
355
+ .then(data => {
356
+ const customers = Array.isArray(data) ? data : (data.data || []);
357
+
358
+ if (customers.length > 0) {
359
+ let html = '';
360
+ customers.forEach(customer => {
361
+ html += '<tr>';
362
+ html += '<td>' + (customer.ecc_id || customer.id || '-') + '</td>';
363
+ html += '<td>' + (customer.name1 || customer.name || '-') + '</td>';
364
+ html += '<td>' + (customer.tel || customer.phone || '-') + '</td>';
365
+ html += '<td>' + (customer.mail1 || customer.email || '-') + '</td>';
366
+ html += '<td>' + (customer.zip1 || customer.zip || '-') + '</td>';
367
+ html += '<td>' + (customer.address1 || customer.address || '-') + '</td>';
368
+ html += '<td>' +
369
+ '<button class="btn btn-sm btn-info mr-1" onclick="alert(\'顧客ID: ' + (customer.ecc_id || customer.id) + '\')">📋 詳細</button>' +
370
+ '<button class="btn btn-sm btn-primary" onclick="alert(\'編集機能は準備中です\')">✏️ 編集</button>' +
371
+ '</td>';
372
+ html += '</tr>';
373
+ });
374
+ tableBody.innerHTML = html;
375
+ infoDiv.innerHTML = '<small>✅ ' + customers.length + '件の顧客が見つかりました</small>';
376
+
377
+ if (window.saveApiLogToSupabase) {
378
+ saveApiLogToSupabase('顧客検索テーブル', endpoint, requestData, { count: customers.length }, 'success');
379
+ }
380
+ } else {
381
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">📭 検索結果なし</td></tr>';
382
+ infoDiv.innerHTML = '<small>検索結果: 0件</small>';
383
+ }
384
+ })
385
+ .catch(err => {
386
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-danger">❌ エラー: ' + err.message + '</td></tr>';
387
+ infoDiv.innerHTML = '<small class="text-danger">データ読込エラー</small>';
388
+
389
+ if (window.saveApiLogToSupabase) {
390
+ saveApiLogToSupabase('顧客検索テーブル', endpoint, requestData, { error: err.message }, 'error');
391
+ }
392
+ });
393
+ }
394
+
395
+ // 顧客テーブルクリア
396
+ function clearCustomerTable(button) {
397
+ const block = button.closest('.shop11-api-block');
398
+ block.querySelector('.customer-search-keyword').value = '';
399
+ block.querySelector('.customer-table-body').innerHTML = '<tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr>';
400
+ block.querySelector('.customer-table-info').innerHTML = '';
401
+ }
402
+
403
+ // 査定データテーブル読込
404
+ function loadSateiTable(button) {
405
+ const block = button.closest('.shop11-api-block');
406
+ const keyword = block.querySelector('.satei-search-keyword').value;
407
+ const tableBody = block.querySelector('.satei-table-body');
408
+ const infoDiv = block.querySelector('.satei-table-info');
409
+
410
+ if (!keyword) {
411
+ alert('検索キーワードを入力してください');
412
+ return;
413
+ }
414
+
415
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center">🔍 検索中...</td></tr>';
416
+
417
+ const endpoint = '/api/satei/search?keyword=' + encodeURIComponent(keyword);
418
+ const requestData = { keyword };
419
+
420
+ fetch(endpoint, {
421
+ method: 'GET',
422
+ headers: { 'Content-Type': 'application/json' }
423
+ })
424
+ .then(res => res.json())
425
+ .then(data => {
426
+ const sateiList = Array.isArray(data) ? data : (data.data || []);
427
+
428
+ if (sateiList.length > 0) {
429
+ let html = '';
430
+ sateiList.forEach(satei => {
431
+ html += '<tr>';
432
+ html += '<td>' + (satei.id || '-') + '</td>';
433
+ html += '<td>' + (satei.product_name || satei.title || '-') + '</td>';
434
+ html += '<td>' + (satei.customer_name || satei.ecc_name || '-') + '</td>';
435
+ html += '<td class="text-right">' + (satei.price ? '¥' + parseInt(satei.price).toLocaleString() : '-') + '</td>';
436
+ html += '<td>' + (satei.satei_date || satei.created_at || '-') + '</td>';
437
+ html += '<td>' + getSateiStatusBadge(satei.status) + '</td>';
438
+ html += '<td>' +
439
+ '<button class="btn btn-sm btn-info mr-1" onclick="alert(\'査定ID: ' + (satei.id) + '\')">📋 詳細</button>' +
440
+ '<button class="btn btn-sm btn-warning" onclick="alert(\'編集機能は準備中です\')">✏️ 編集</button>' +
441
+ '</td>';
442
+ html += '</tr>';
443
+ });
444
+ tableBody.innerHTML = html;
445
+ infoDiv.innerHTML = '<small>✅ ' + sateiList.length + '件の査定が見つかりました</small>';
446
+
447
+ if (window.saveApiLogToSupabase) {
448
+ saveApiLogToSupabase('査定検索テーブル', endpoint, requestData, { count: sateiList.length }, 'success');
449
+ }
450
+ } else {
451
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">📭 検索結果なし</td></tr>';
452
+ infoDiv.innerHTML = '<small>検索結果: 0件</small>';
453
+ }
454
+ })
455
+ .catch(err => {
456
+ tableBody.innerHTML = '<tr><td colspan="7" class="text-center text-danger">❌ エラー: ' + err.message + '</td></tr>';
457
+ infoDiv.innerHTML = '<small class="text-danger">データ読込エラー</small>';
458
+
459
+ if (window.saveApiLogToSupabase) {
460
+ saveApiLogToSupabase('査定検索テーブル', endpoint, requestData, { error: err.message }, 'error');
461
+ }
462
+ });
463
+ }
464
+
465
+ // 査定テーブルクリア
466
+ function clearSateiTable(button) {
467
+ const block = button.closest('.shop11-api-block');
468
+ block.querySelector('.satei-search-keyword').value = '';
469
+ block.querySelector('.satei-table-body').innerHTML = '<tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr>';
470
+ block.querySelector('.satei-table-info').innerHTML = '';
471
+ }
472
+
473
+ // 査定ステータスバッジ
474
+ function getSateiStatusBadge(status) {
475
+ const statusMap = {
476
+ '1': { label: '査定中', color: 'info' },
477
+ '2': { label: '査定完了', color: 'success' },
478
+ '3': { label: '買取完了', color: 'primary' },
479
+ '4': { label: 'キ��ンセル', color: 'danger' }
480
+ };
481
+ const statusInfo = statusMap[status] || { label: status || '-', color: 'secondary' };
482
+ return '<span class="badge badge-' + statusInfo.color + '">' + statusInfo.label + '</span>';
483
+ }
484
+
485
+ // 商品登録
486
+ function registerProduct(button) {
487
+ const block = button.closest('.shop11-api-block');
488
+ const form = block.querySelector('.product-register-form');
489
+ const resultDiv = block.querySelector('.register-result');
490
+
491
+ const name = block.querySelector('.product-name').value;
492
+ const brand = block.querySelector('.product-brand').value;
493
+ const category = block.querySelector('.product-category').value;
494
+ const price = block.querySelector('.product-price').value;
495
+ const description = block.querySelector('.product-description').value;
496
+ const status = block.querySelector('.product-status').value;
497
+
498
+ if (!name || !price) {
499
+ alert('商品名と価格は必須です');
500
+ return;
501
+ }
502
+
503
+ resultDiv.style.display = 'block';
504
+ resultDiv.innerHTML = '💾 登録中...';
505
+
506
+ const endpoint = '/api/shouhin/register';
507
+ const requestData = {
508
+ name: name,
509
+ brand: brand,
510
+ category: category,
511
+ price: price,
512
+ description: description,
513
+ status: status
514
+ };
515
+
516
+ fetch(endpoint, {
517
+ method: 'POST',
518
+ headers: {
519
+ 'Content-Type': 'application/json',
520
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
521
+ },
522
+ body: JSON.stringify(requestData)
523
+ })
524
+ .then(res => res.json())
525
+ .then(data => {
526
+ resultDiv.innerHTML = '<div class="alert alert-success">✅ 商品を登録しました!<br>商品ID: ' + (data.product_id || data.id || '不明') + '</div>';
527
+ form.reset();
528
+
529
+ if (window.saveApiLogToSupabase) {
530
+ saveApiLogToSupabase('商品登録', endpoint, requestData, data, 'success');
531
+ }
532
+ })
533
+ .catch(err => {
534
+ resultDiv.innerHTML = '<div class="alert alert-danger">❌ エラー: ' + err.message + '</div>';
535
+
536
+ if (window.saveApiLogToSupabase) {
537
+ saveApiLogToSupabase('商品登録', endpoint, requestData, { error: err.message }, 'error');
538
+ }
539
+ });
540
+ }
app.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import os
4
+ from datetime import datetime
5
+
6
+ # Gradio App for API Workflow Builder
7
+ # Visual page builder with drag-and-drop interface
8
+
9
+ def load_page_builder():
10
+ """Load the main page builder HTML"""
11
+ with open('index.html', 'r', encoding='utf-8') as f:
12
+ return f.read()
13
+
14
+ def save_workflow(workflow_name, html_content, css_content):
15
+ """Save workflow to JSON file"""
16
+ if not workflow_name:
17
+ return "❌ ワークフロー名を入力してください"
18
+
19
+ workflows_dir = 'workflows'
20
+ os.makedirs(workflows_dir, exist_ok=True)
21
+
22
+ workflow_data = {
23
+ 'name': workflow_name,
24
+ 'html': html_content,
25
+ 'css': css_content,
26
+ 'created_at': datetime.now().isoformat(),
27
+ 'updated_at': datetime.now().isoformat()
28
+ }
29
+
30
+ filename = f"{workflows_dir}/{workflow_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
31
+
32
+ with open(filename, 'w', encoding='utf-8') as f:
33
+ json.dump(workflow_data, f, ensure_ascii=False, indent=2)
34
+
35
+ return f"✅ ワークフロー保存完了: {filename}"
36
+
37
+ def load_workflows():
38
+ """Load list of saved workflows"""
39
+ workflows_dir = 'workflows'
40
+ if not os.path.exists(workflows_dir):
41
+ return []
42
+
43
+ workflows = []
44
+ for filename in os.listdir(workflows_dir):
45
+ if filename.endswith('.json'):
46
+ with open(f"{workflows_dir}/{filename}", 'r', encoding='utf-8') as f:
47
+ data = json.load(f)
48
+ workflows.append({
49
+ 'filename': filename,
50
+ 'name': data.get('name', 'Unknown'),
51
+ 'created_at': data.get('created_at', 'Unknown')
52
+ })
53
+
54
+ return workflows
55
+
56
+ def export_html(workflow_name, html_content, css_content):
57
+ """Export complete HTML file"""
58
+ full_html = f"""<!DOCTYPE html>
59
+ <html lang="ja">
60
+ <head>
61
+ <meta charset="UTF-8">
62
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
63
+ <title>{workflow_name}</title>
64
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
65
+ <style>{css_content}</style>
66
+ </head>
67
+ <body>
68
+ {html_content}
69
+ <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
70
+ </body>
71
+ </html>"""
72
+
73
+ output_dir = 'exports'
74
+ os.makedirs(output_dir, exist_ok=True)
75
+
76
+ filename = f"{output_dir}/{workflow_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
77
+
78
+ with open(filename, 'w', encoding='utf-8') as f:
79
+ f.write(full_html)
80
+
81
+ return filename
82
+
83
+ # Gradio Interface
84
+ with gr.Blocks(title="🚀 API Workflow Builder", theme=gr.themes.Soft()) as app:
85
+ gr.Markdown("""
86
+ # 🚀 API Workflow Builder
87
+
88
+ Visual page builder with drag-and-drop interface powered by GrapeJS.
89
+ Create API workflow screens without coding!
90
+
91
+ ## ✨ Features
92
+ - 📦 Drag & Drop page builder
93
+ - 🔌 API integration blocks
94
+ - 💾 Save workflows to JSON/Supabase
95
+ - 📥 Export as HTML file
96
+ - 🎨 Visual styling editor
97
+ """)
98
+
99
+ with gr.Tab("📝 Page Builder"):
100
+ gr.Markdown("### Visual Editor")
101
+ gr.Markdown("Open the editor below to start building your workflow pages.")
102
+
103
+ # Page Builder iframe
104
+ page_builder_html = gr.HTML(
105
+ value="""
106
+ <div style="width: 100%; height: 800px; border: 2px solid #e2e8f0; border-radius: 8px; overflow: hidden;">
107
+ <iframe
108
+ src="/file=index.html"
109
+ style="width: 100%; height: 100%; border: none;"
110
+ sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
111
+ ></iframe>
112
+ </div>
113
+ """,
114
+ label="Page Builder Editor"
115
+ )
116
+
117
+ gr.Markdown("""
118
+ ### 使い方
119
+ 1. 左サイドバーから要素をドラッグ&ドロップ
120
+ 2. 右サイドバーでスタイルをカスタマイズ
121
+ 3. ヘッダーの「保存」ボタンでSupabaseに保存
122
+ 4. 下の「エクスポート」タブからHTMLファイルをダウンロード
123
+ """)
124
+
125
+ with gr.Tab("💾 Workflows"):
126
+ gr.Markdown("### 保存されたワークフロー")
127
+
128
+ with gr.Row():
129
+ workflow_name_input = gr.Textbox(
130
+ label="ワークフロー名",
131
+ placeholder="my-workflow"
132
+ )
133
+ save_btn = gr.Button("💾 保存", variant="primary")
134
+
135
+ save_status = gr.Textbox(label="保存ステータス", interactive=False)
136
+
137
+ gr.Markdown("### ワークフロー一覧")
138
+ workflows_list = gr.JSON(label="保存済みワークフロー")
139
+
140
+ refresh_btn = gr.Button("🔄 更新")
141
+
142
+ # Event handlers
143
+ refresh_btn.click(
144
+ fn=lambda: load_workflows(),
145
+ outputs=workflows_list
146
+ )
147
+
148
+ with gr.Tab("📥 Export"):
149
+ gr.Markdown("### HTMLエクスポート")
150
+
151
+ with gr.Row():
152
+ export_name = gr.Textbox(
153
+ label="ファイル名",
154
+ placeholder="my-page"
155
+ )
156
+ html_input = gr.Textbox(
157
+ label="HTML Content",
158
+ placeholder="<div>...</div>",
159
+ lines=5
160
+ )
161
+ css_input = gr.Textbox(
162
+ label="CSS Content",
163
+ placeholder=".class { ... }",
164
+ lines=5
165
+ )
166
+
167
+ export_btn = gr.Button("📥 HTMLエクスポート", variant="primary")
168
+ export_result = gr.File(label="ダウンロード")
169
+
170
+ export_btn.click(
171
+ fn=export_html,
172
+ inputs=[export_name, html_input, css_input],
173
+ outputs=export_result
174
+ )
175
+
176
+ with gr.Tab("⚙️ Settings"):
177
+ gr.Markdown("### API設定")
178
+
179
+ with gr.Row():
180
+ with gr.Column():
181
+ supabase_url = gr.Textbox(
182
+ label="Supabase URL",
183
+ value="https://rootomzbucovwdqsscqd.supabase.co"
184
+ )
185
+ supabase_key = gr.Textbox(
186
+ label="Supabase Anon Key",
187
+ value="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
188
+ type="password"
189
+ )
190
+
191
+ with gr.Column():
192
+ api_base_url = gr.Textbox(
193
+ label="API Base URL",
194
+ value="https://your-api.com",
195
+ placeholder="https://api.example.com"
196
+ )
197
+ api_auth_type = gr.Dropdown(
198
+ label="認証タイプ",
199
+ choices=["None", "Bearer Token", "API Key", "Basic Auth"],
200
+ value="None"
201
+ )
202
+
203
+ save_config_btn = gr.Button("💾 設定保存", variant="primary")
204
+ config_status = gr.Textbox(label="保存ステータス", interactive=False)
205
+
206
+ with gr.Tab("📚 Documentation"):
207
+ gr.Markdown("""
208
+ ## 📖 ドキュメント
209
+
210
+ ### 🎯 概要
211
+ API Workflow Builderは、ドラッグ&ドロップでAPIワークフロー画面を作成できるノーコードツールです。
212
+
213
+ ### 🚀 クイックスタート
214
+
215
+ 1. **Page Builderタブ**を開く
216
+ 2. 左サイドバーから要素をドラッグ
217
+ 3. キャンバスにドロップして配置
218
+ 4. スタイルをカスタマイズ
219
+ 5. 保存またはエクスポート
220
+
221
+ ### 📦 利用可能なブロック
222
+
223
+ #### Shop11 API カテゴリ
224
+ - **地金チェック削除**: 商品IDで地金チェックを削除
225
+ - **査定タイトル生成**: 商品IDから査定タイトルを生成
226
+ - **メール送信**: メール通知を送信
227
+ - **商品検索**: キーワードで商品を検索
228
+ - **商品データテーブル**: 商品データを検索・表示
229
+ - **顧客検索テーブル**: 顧客情報を検索・表示
230
+ - **査定検索テーブル**: 査定データを検索・表示
231
+ - **商品登録フォーム**: 新規商品を登録
232
+
233
+ ### 🔌 API連携
234
+
235
+ すべてのAPIブロックは自動的にSupabaseにログを保存します。
236
+
237
+ ```javascript
238
+ // API呼び出し例
239
+ fetch('/api/your-endpoint', {
240
+ method: 'POST',
241
+ headers: { 'Content-Type': 'application/json' },
242
+ body: JSON.stringify(data)
243
+ })
244
+ .then(res => res.json())
245
+ .then(data => {
246
+ // Supabaseログ保存
247
+ saveApiLogToSupabase('API名', endpoint, request, response, 'success');
248
+ });
249
+ ```
250
+
251
+ ### 💾 データ保存
252
+
253
+ ページデータは以下の形式でSupabaseに保存されます:
254
+
255
+ ```json
256
+ {
257
+ "name": "page-name",
258
+ "html_content": "<div>...</div>",
259
+ "css_content": ".class {...}",
260
+ "components_json": {...},
261
+ "created_at": "2026-02-23T...",
262
+ "updated_at": "2026-02-23T..."
263
+ }
264
+ ```
265
+
266
+ ### 🎨 カスタマイズ
267
+
268
+ 新しいブロックを追加するには:
269
+
270
+ 1. `index.html`の`blockManager.add()`に新しいブロックを登録
271
+ 2. `api-functions.js`にAPI呼び出し関数を追加
272
+ 3. イベントリスナーに`data-action`を登録
273
+
274
+ ### 🔐 セキュリティ
275
+
276
+ - Supabase RLSポリシーで行レベルセキュリティを設定
277
+ - API Keyは環境変数で管理
278
+ - CSRF Tokenを使用したAPI保護
279
+
280
+ ### 📊 技術スタック
281
+
282
+ - **Frontend**: GrapeJS, Bootstrap, jQuery
283
+ - **Backend**: Gradio (Python)
284
+ - **Database**: Supabase (PostgreSQL)
285
+ - **Hosting**: Hugging Face Spaces
286
+
287
+ ### 🐛 トラブルシューティング
288
+
289
+ **ブロックがドラッグできない**
290
+ - ブラウザのキャッシュをクリア
291
+ - ページをリロード
292
+
293
+ **APIエラー**
294
+ - ネットワークタブでHTTPステータス確認
295
+ - Supabase接続情報を確認
296
+
297
+ **保存できない**
298
+ - RLSポリシーを確認
299
+ - ブラウザコンソールのエラーログをチェック
300
+
301
+ ### 📝 ライセンス
302
+
303
+ MIT License
304
+
305
+ ### 👤 作者
306
+
307
+ Created for visual API workflow automation.
308
+
309
+ ### 🔗 リンク
310
+
311
+ - [GitHub Repository](https://github.com/your-repo)
312
+ - [Supabase Docs](https://supabase.com/docs)
313
+ - [GrapeJS Docs](https://grapesjs.com/docs/)
314
+ """)
315
+
316
+ # Launch app
317
+ if __name__ == "__main__":
318
+ app.launch(
319
+ server_name="0.0.0.0",
320
+ server_port=7860,
321
+ share=False,
322
+ show_error=True
323
+ )
config.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "app": {
3
+ "name": "API Workflow Builder",
4
+ "version": "1.0.0",
5
+ "description": "Visual page builder for API workflows"
6
+ },
7
+ "supabase": {
8
+ "url": "https://rootomzbucovwdqsscqd.supabase.co",
9
+ "anonKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvb3RvbXpidWNvdndkcXNzY3FkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzU4OTE4ODMsImV4cCI6MjA1MTQ2Nzg4M30.fYKOe-HPh4WUdvBhEJxakLWCMQBp4E90EDwARk7ucf8",
10
+ "tables": {
11
+ "pages": "page_builder_pages",
12
+ "logs": "api_execution_logs"
13
+ }
14
+ },
15
+ "api": {
16
+ "baseUrl": "https://your-api.com",
17
+ "authType": "none",
18
+ "authToken": "",
19
+ "endpoints": {
20
+ "productSearch": "/api/products/search",
21
+ "customerSearch": "/api/customers/search",
22
+ "assessmentSearch": "/api/assessments/search",
23
+ "productRegister": "/api/products/register",
24
+ "emailNotification": "/api/notifications/email"
25
+ }
26
+ },
27
+ "blocks": {
28
+ "categories": [
29
+ {
30
+ "id": "api",
31
+ "label": "API Blocks",
32
+ "enabled": true
33
+ },
34
+ {
35
+ "id": "ui",
36
+ "label": "UI Components",
37
+ "enabled": true
38
+ },
39
+ {
40
+ "id": "custom",
41
+ "label": "Custom Blocks",
42
+ "enabled": false
43
+ }
44
+ ]
45
+ },
46
+ "editor": {
47
+ "autosave": true,
48
+ "storageType": "local",
49
+ "canvas": {
50
+ "styles": [
51
+ "https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css"
52
+ ],
53
+ "scripts": [
54
+ "https://code.jquery.com/jquery-3.5.1.min.js"
55
+ ]
56
+ }
57
+ }
58
+ }
exports/.gitkeep ADDED
File without changes
index.html ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>API Workflow Builder - Visual Page Editor</title>
7
+
8
+ <!-- GrapeJS CSS -->
9
+ <link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css">
10
+ <link rel="stylesheet" href="https://unpkg.com/grapesjs-blocks-basic/dist/grapesjs-blocks-basic.min.css">
11
+
12
+ <style>
13
+ body, html {
14
+ height: 100%;
15
+ margin: 0;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
17
+ }
18
+
19
+ #header {
20
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
21
+ color: white;
22
+ padding: 15px 30px;
23
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ }
28
+
29
+ #header h1 {
30
+ margin: 0;
31
+ font-size: 24px;
32
+ font-weight: 600;
33
+ }
34
+
35
+ #header-buttons {
36
+ display: flex;
37
+ gap: 10px;
38
+ }
39
+
40
+ #header button {
41
+ padding: 10px 20px;
42
+ border: none;
43
+ border-radius: 6px;
44
+ cursor: pointer;
45
+ font-size: 14px;
46
+ font-weight: 500;
47
+ transition: all 0.3s;
48
+ }
49
+
50
+ .btn-preview {
51
+ background: #ffffff;
52
+ color: #667eea;
53
+ }
54
+
55
+ .btn-save {
56
+ background: #48bb78;
57
+ color: white;
58
+ }
59
+
60
+ .btn-load {
61
+ background: #4299e1;
62
+ color: white;
63
+ }
64
+
65
+ .btn-clear {
66
+ background: #f56565;
67
+ color: white;
68
+ }
69
+
70
+ #header button:hover {
71
+ transform: translateY(-2px);
72
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
73
+ }
74
+
75
+ #gjs {
76
+ height: calc(100vh - 70px);
77
+ overflow: hidden;
78
+ }
79
+
80
+ /* Shop11 API Block カスタムスタイル */
81
+ .shop11-api-block {
82
+ padding: 20px;
83
+ margin: 15px 0;
84
+ border: 2px solid #e2e8f0;
85
+ border-radius: 8px;
86
+ background: #f7fafc;
87
+ }
88
+
89
+ .shop11-api-block h3 {
90
+ margin-top: 0;
91
+ color: #2d3748;
92
+ border-bottom: 2px solid #667eea;
93
+ padding-bottom: 10px;
94
+ }
95
+
96
+ .api-button {
97
+ background: #667eea;
98
+ color: white;
99
+ border: none;
100
+ padding: 10px 20px;
101
+ border-radius: 5px;
102
+ cursor: pointer;
103
+ font-weight: 500;
104
+ transition: background 0.3s;
105
+ }
106
+
107
+ .api-button:hover {
108
+ background: #5a67d8;
109
+ }
110
+
111
+ .api-result {
112
+ margin-top: 15px;
113
+ padding: 15px;
114
+ background: white;
115
+ border-radius: 5px;
116
+ border: 1px solid #e2e8f0;
117
+ max-height: 300px;
118
+ overflow-y: auto;
119
+ }
120
+
121
+ /* モーダルスタイル */
122
+ .modal {
123
+ display: none;
124
+ position: fixed;
125
+ z-index: 9999;
126
+ left: 0;
127
+ top: 0;
128
+ width: 100%;
129
+ height: 100%;
130
+ background-color: rgba(0,0,0,0.5);
131
+ }
132
+
133
+ .modal-content {
134
+ background-color: #fefefe;
135
+ margin: 5% auto;
136
+ padding: 30px;
137
+ border-radius: 10px;
138
+ width: 80%;
139
+ max-width: 600px;
140
+ max-height: 80vh;
141
+ overflow-y: auto;
142
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
143
+ }
144
+
145
+ .close {
146
+ color: #aaa;
147
+ float: right;
148
+ font-size: 28px;
149
+ font-weight: bold;
150
+ cursor: pointer;
151
+ }
152
+
153
+ .close:hover {
154
+ color: #000;
155
+ }
156
+
157
+ .page-item {
158
+ padding: 15px;
159
+ margin: 10px 0;
160
+ background: #f7fafc;
161
+ border-radius: 5px;
162
+ border: 1px solid #e2e8f0;
163
+ cursor: pointer;
164
+ transition: all 0.3s;
165
+ }
166
+
167
+ .page-item:hover {
168
+ background: #edf2f7;
169
+ border-color: #667eea;
170
+ }
171
+ </style>
172
+ </head>
173
+ <body>
174
+ <div id="header">
175
+ <h1>🚀 API Workflow Builder</h1>
176
+ <div id="header-buttons">
177
+ <button class="btn-preview" onclick="previewPage()">👁️ プレビュー</button>
178
+ <button class="btn-load" onclick="loadPageList()">📂 読込</button>
179
+ <button class="btn-save" onclick="savePage()">💾 保存</button>
180
+ <button class="btn-clear" onclick="clearPage()">🗑️ クリア</button>
181
+ </div>
182
+ </div>
183
+
184
+ <div id="gjs"></div>
185
+
186
+ <!-- ページ一覧モーダル -->
187
+ <div id="pageListModal" class="modal">
188
+ <div class="modal-content">
189
+ <span class="close" onclick="closeModal()">&times;</span>
190
+ <h2>📂 保存されたページ</h2>
191
+ <div id="pageList"></div>
192
+ </div>
193
+ </div>
194
+
195
+ <!-- Supabase JS SDK -->
196
+ <script src="https://unpkg.com/@supabase/supabase-js@2"></script>
197
+
198
+ <!-- GrapeJS -->
199
+ <script src="https://unpkg.com/grapesjs"></script>
200
+ <script src="https://unpkg.com/grapesjs-blocks-basic"></script>
201
+
202
+ <!-- Supabase Config -->
203
+ <script src="/shop11/public/page-builder/supabase-config.js"></script>
204
+
205
+ <script>
206
+ // GrapeJS エディタ初期化
207
+ const editor = grapesjs.init({
208
+ container: '#gjs',
209
+ fromElement: false,
210
+ height: '100%',
211
+ width: 'auto',
212
+ plugins: ['gjs-blocks-basic'],
213
+ storageManager: {
214
+ type: 'local',
215
+ autosave: true,
216
+ autoload: true,
217
+ stepsBeforeSave: 1
218
+ },
219
+ canvas: {
220
+ styles: [
221
+ 'https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css'
222
+ ],
223
+ scripts: [
224
+ 'https://code.jquery.com/jquery-3.5.1.min.js',
225
+ '/shop11/public/page-builder/api-functions.js'
226
+ ]
227
+ }
228
+ });
229
+
230
+ // カスタムブロック登録
231
+ editor.on('load', function() {
232
+ const blockManager = editor.BlockManager;
233
+
234
+ // 地金チェック削除ブロック
235
+ blockManager.add('gold-check-delete', {
236
+ label: '地金チェック削除',
237
+ category: 'Shop11 API',
238
+ content: '<div class="shop11-api-block" data-api="gold_check"><h3>🥇 地金チェック削除</h3><div class="form-group"><label>商品ID:</label><input type="text" class="form-control gold-product-id" placeholder="商品IDを入力"></div><button class="btn btn-danger" data-action="goldCheckDelete">削除実行</button><div class="api-result gold-check-result" style="display:none;"></div></div>',
239
+ attributes: { class: 'fa fa-trash' }
240
+ });
241
+
242
+ // 査定タイトル生成ブロック
243
+ blockManager.add('satei-title', {
244
+ label: '査定タイトル生成',
245
+ category: 'Shop11 API',
246
+ content: '<div class="shop11-api-block" data-api="satei_title"><h3>📝 査定タイトル生成</h3><div class="form-group"><label>商品ID:</label><input type="text" class="form-control satei-product-id" placeholder="商品IDを入力"></div><button class="btn btn-primary" data-action="createSateiTitle">タイトル生成</button><div class="api-result satei-title-result" style="display:none;"></div></div>',
247
+ attributes: { class: 'fa fa-file-text' }
248
+ });
249
+
250
+ // メール送信ブロック
251
+ blockManager.add('send-notification', {
252
+ label: 'メール送信',
253
+ category: 'Shop11 API',
254
+ content: '<div class="shop11-api-block" data-api="notification"><h3>📧 メール送信</h3><div class="form-group"><label>宛先:</label><input type="email" class="form-control mail-to" placeholder="email@example.com"></div><div class="form-group"><label>件名:</label><input type="text" class="form-control mail-subject" placeholder="件名を入力"></div><div class="form-group"><label>本文:</label><textarea class="form-control mail-body" rows="4" placeholder="メッセージを入力"></textarea></div><button class="btn btn-success" data-action="sendNotification">送信</button><div class="api-result mail-result" style="display:none;"></div></div>',
255
+ attributes: { class: 'fa fa-envelope' }
256
+ });
257
+
258
+ // 商品検索ブロック
259
+ blockManager.add('product-search', {
260
+ label: '商品検索',
261
+ category: 'Shop11 API',
262
+ content: '<div class="shop11-api-block" data-api="product_search"><h3>🔍 商品検索</h3><div class="form-group"><label>検索キーワード:</label><input type="text" class="form-control search-keyword" placeholder="商品名・ブランドで検索"></div><button class="btn btn-info" data-action="searchProduct">検索</button><div class="api-result search-result" style="display:none;"></div></div>',
263
+ attributes: { class: 'fa fa-search' }
264
+ });
265
+
266
+ // データテーブルブロック
267
+ blockManager.add('data-table', {
268
+ label: 'データテーブル',
269
+ category: 'Shop11 UI',
270
+ content: '<div class="container my-4"><h3>データ一覧</h3><table class="table table-striped table-bordered"><thead class="thead-dark"><tr><th>ID</th><th>商品名</th><th>ステータス</th><th>金額</th><th>操作</th></tr></thead><tbody><tr><td>1</td><td>サンプル商品A</td><td><span class="badge badge-success">完了</span></td><td>¥10,000</td><td><button class="btn btn-sm btn-primary">編集</button></td></tr><tr><td>2</td><td>サンプル商品B</td><td><span class="badge badge-warning">処理中</span></td><td>¥25,000</td><td><button class="btn btn-sm btn-primary">編集</button></td></tr></tbody></table></div>',
271
+ attributes: { class: 'fa fa-table' }
272
+ });
273
+
274
+ // 商品データテーブルブロック
275
+ blockManager.add('product-data-table', {
276
+ label: '商品データテーブル',
277
+ category: 'Shop11 API',
278
+ content: '<div class="shop11-api-block" data-api="product_table"><h3>商品データテーブル</h3><div class="form-group"><label>検索キーワード:</label><div class="input-group"><input type="text" class="form-control product-search-keyword" placeholder="商品名・ブランドで検索"><div class="input-group-append"><button class="btn btn-primary" data-action="loadProductTable">🔍 検索</button><button class="btn btn-secondary ml-2" data-action="clearProductTable">🗑️ クリア</button></div></div></div><div class="product-table-container"><table class="table table-striped table-bordered table-hover"><thead class="thead-dark"><tr><th>商品ID</th><th>商品名</th><th>ブランド</th><th>カテゴリ</th><th>価格</th><th>ステータス</th><th>操作</th></tr></thead><tbody class="product-table-body"><tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr></tbody></table></div><div class="product-table-info mt-2 text-muted"></div></div>',
279
+ attributes: { class: 'fa fa-database' }
280
+ });
281
+
282
+ // 顧客検索テーブルブロック
283
+ blockManager.add('customer-data-table', {
284
+ label: '顧客検索テーブル',
285
+ category: 'Shop11 API',
286
+ content: '<div class="shop11-api-block" data-api="customer_table"><h3>👤 顧客検索テーブル</h3><div class="form-group"><label>顧客名・電話番号:</label><div class="input-group"><input type="text" class="form-control customer-search-keyword" placeholder="顧客名・電話番号で検索"><div class="input-group-append"><button class="btn btn-primary" data-action="loadCustomerTable">🔍 検索</button><button class="btn btn-secondary ml-2" data-action="clearCustomerTable">🗑️ クリア</button></div></div></div><div class="customer-table-container"><table class="table table-striped table-bordered table-hover"><thead class="thead-dark"><tr><th>顧客ID</th><th>顧客名</th><th>電話番号</th><th>メール</th><th>郵便番号</th><th>住所</th><th>操作</th></tr></thead><tbody class="customer-table-body"><tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr></tbody></table></div><div class="customer-table-info mt-2 text-muted"></div></div>',
287
+ attributes: { class: 'fa fa-users' }
288
+ });
289
+
290
+ // 査定検索テーブルブロック
291
+ blockManager.add('satei-data-table', {
292
+ label: '査定検索テーブル',
293
+ category: 'Shop11 API',
294
+ content: '<div class="shop11-api-block" data-api="satei_table"><h3>📝 査定検索テーブル</h3><div class="form-group"><label>検索条件:</label><div class="input-group"><input type="text" class="form-control satei-search-keyword" placeholder="商品名・顧客名で検索"><div class="input-group-append"><button class="btn btn-primary" data-action="loadSateiTable">🔍 検索</button><button class="btn btn-secondary ml-2" data-action="clearSateiTable">🗑️ クリア</button></div></div></div><div class="satei-table-container"><table class="table table-striped table-bordered table-hover"><thead class="thead-dark"><tr><th>査定ID</th><th>商品名</th><th>顧客名</th><th>査定額</th><th>査定日</th><th>ステータス</th><th>操作</th></tr></thead><tbody class="satei-table-body"><tr><td colspan="7" class="text-center text-muted">検索キーワードを入力して検索してください</td></tr></tbody></table></div><div class="satei-table-info mt-2 text-muted"></div></div>',
295
+ attributes: { class: 'fa fa-clipboard' }
296
+ });
297
+
298
+ // 商品登録フォームブロック
299
+ blockManager.add('product-register-form', {
300
+ label: '商品登録フォーム',
301
+ category: 'Shop11 API',
302
+ content: '<div class="shop11-api-block" data-api="product_register"><h3>✏️ 商品登録フォーム</h3><form class="product-register-form"><div class="row"><div class="col-md-6"><div class="form-group"><label>商品名 <span class="text-danger">*</span></label><input type="text" class="form-control product-name" placeholder="商品名を入力" required></div></div><div class="col-md-6"><div class="form-group"><label>ブランド</label><input type="text" class="form-control product-brand" placeholder="ブランド名"></div></div></div><div class="row"><div class="col-md-6"><div class="form-group"><label>カテゴリ</label><select class="form-control product-category"><option value="">選択してください</option><option value="1">ジュエリー</option><option value="2">時計</option><option value="3">バッグ</option><option value="4">貴金属</option><option value="5">その他</option></select></div></div><div class="col-md-6"><div class="form-group"><label>価格 <span class="text-danger">*</span></label><input type="number" class="form-control product-price" placeholder="価格を入力" required></div></div></div><div class="form-group"><label>商品説明</label><textarea class="form-control product-description" rows="3" placeholder="商品の説明を入力"></textarea></div><div class="form-group"><label>ステータス</label><select class="form-control product-status"><option value="1" selected>在庫あり</option><option value="2">予約中</option><option value="3">売約済</option><option value="4">出品中</option></select></div><div class="text-right"><button type="button" class="btn btn-success btn-lg" data-action="registerProduct">💾 登録する</button><button type="reset" class="btn btn-secondary btn-lg ml-2">🔄 リセット</button></div></form><div class="register-result mt-3" style="display:none;"></div></div>',
303
+ attributes: { class: 'fa fa-plus-square' }
304
+ });
305
+
306
+ console.log('✅ API Workflow Builder loaded!');
307
+ console.log('📦 Total Blocks:', blockManager.getAll().length);
308
+ });
309
+
310
+ // プレビュー機能
311
+ function previewPage() {
312
+ const html = editor.getHtml();
313
+ const css = '<style>' + editor.getCss() + '</style>';
314
+ const previewWindow = window.open('', '_blank');
315
+ previewWindow.document.write(`
316
+ <!DOCTYPE html>
317
+ <html>
318
+ <head>
319
+ <meta charset="UTF-8">
320
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
321
+ <title>Preview</title>
322
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
323
+ ${css}
324
+ </head>
325
+ <body>
326
+ ${html}
327
+ <script src="https://code.jquery.com/jquery-3.5.1.min.js"><\/script>
328
+ <script src="/shop11/public/page-builder/api-functions.js"><\/script>
329
+ </body>
330
+ </html>
331
+ `);
332
+ }
333
+
334
+ // 保存機能
335
+ function savePage() {
336
+ const html = editor.getHtml();
337
+ const css = editor.getCss();
338
+ const components = editor.getComponents();
339
+
340
+ const pageName = prompt('ページ名を入力してください:', 'page-' + Date.now());
341
+ if (!pageName) return;
342
+
343
+ savePageToSupabase(pageName, html, css, components).then(result => {
344
+ if (result.success) {
345
+ alert('✅ Supabaseに保存しました!\nページID: ' + result.data[0].id);
346
+ downloadHtmlFile(pageName, html, css);
347
+ } else {
348
+ alert('❌ 保存エラー: ' + result.error);
349
+ }
350
+ });
351
+ }
352
+
353
+ // HTMLファイルダウンロード
354
+ function downloadHtmlFile(pageName, html, css) {
355
+ const fullHtml = `<!DOCTYPE html>
356
+ <html lang="ja">
357
+ <head>
358
+ <meta charset="UTF-8">
359
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
360
+ <title>${pageName}</title>
361
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
362
+ <style>${css}</style>
363
+ </head>
364
+ <body>
365
+ ${html}
366
+ <script src="https://code.jquery.com/jquery-3.5.1.min.js"><\/script>
367
+ <script src="/shop11/public/page-builder/api-functions.js"><\/script>
368
+ </body>
369
+ </html>`;
370
+
371
+ const blob = new Blob([fullHtml], { type: 'text/html' });
372
+ const url = URL.createObjectURL(blob);
373
+ const a = document.createElement('a');
374
+ a.href = url;
375
+ a.download = pageName + '.html';
376
+ a.click();
377
+ }
378
+
379
+ // ページ一覧読込
380
+ function loadPageList() {
381
+ loadPagesFromSupabase().then(result => {
382
+ if (result.success && result.data.length > 0) {
383
+ const pageListDiv = document.getElementById('pageList');
384
+ pageListDiv.innerHTML = '';
385
+
386
+ result.data.forEach(page => {
387
+ const pageItem = document.createElement('div');
388
+ pageItem.className = 'page-item';
389
+ pageItem.innerHTML = `
390
+ <strong>${page.name}</strong><br>
391
+ <small>作成日: ${new Date(page.created_at).toLocaleString('ja-JP')}</small>
392
+ `;
393
+ pageItem.onclick = () => loadPageFromSupabaseById(page.id);
394
+ pageListDiv.appendChild(pageItem);
395
+ });
396
+
397
+ document.getElementById('pageListModal').style.display = 'block';
398
+ } else {
399
+ alert('保存されたページがありません');
400
+ }
401
+ });
402
+ }
403
+
404
+ // ページ読込
405
+ function loadPageFromSupabaseById(pageId) {
406
+ loadPageFromSupabase(pageId).then(result => {
407
+ if (result.success && result.data) {
408
+ const page = result.data;
409
+ editor.setComponents(page.html_content);
410
+ editor.setStyle(page.css_content);
411
+ closeModal();
412
+ alert('✅ ページを読み込みました: ' + page.name);
413
+ } else {
414
+ alert('❌ 読込エラー: ' + result.error);
415
+ }
416
+ });
417
+ }
418
+
419
+ // モーダルを閉じる
420
+ function closeModal() {
421
+ document.getElementById('pageListModal').style.display = 'none';
422
+ }
423
+
424
+ // クリア機能
425
+ function clearPage() {
426
+ if (confirm('すべてのコンテンツをクリアしますか?')) {
427
+ editor.setComponents('');
428
+ editor.setStyle('');
429
+ alert('✅ クリアしました');
430
+ }
431
+ }
432
+
433
+ // モーダル外クリックで閉じる
434
+ window.onclick = function(event) {
435
+ const modal = document.getElementById('pageListModal');
436
+ if (event.target == modal) {
437
+ closeModal();
438
+ }
439
+ }
440
+ </script>
441
+ </body>
442
+ </html>
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio>=4.0.0
supabase-config.js ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Supabase Configuration and API Functions
2
+ // API Workflow Builder - Database Integration
3
+
4
+ const SUPABASE_URL = 'https://rootomzbucovwdqsscqd.supabase.co';
5
+ const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJvb3RvbXpidWNvdndkcXNzY3FkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzU4OTE4ODMsImV4cCI6MjA1MTQ2Nzg4M30.fYKOe-HPh4WUdvBhEJxakLWCMQBp4E90EDwARk7ucf8';
6
+
7
+ // Supabaseクライアント初期化
8
+ const supabaseClient = supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
9
+
10
+ console.log('✅ Supabase Client initialized');
11
+
12
+ /**
13
+ * ページをSupabaseに保存
14
+ * @param {string} pageName - ページ名
15
+ * @param {string} html - HTML内容
16
+ * @param {string} css - CSS内容
17
+ * @param {object} components - GrapeJSコンポーネント
18
+ * @returns {Promise<{success: boolean, data: any, error: string}>}
19
+ */
20
+ async function savePageToSupabase(pageName, html, css, components) {
21
+ try {
22
+ const { data, error } = await supabaseClient
23
+ .from('page_builder_pages')
24
+ .insert({
25
+ name: pageName,
26
+ html_content: html,
27
+ css_content: css,
28
+ components_json: components,
29
+ created_at: new Date().toISOString(),
30
+ updated_at: new Date().toISOString()
31
+ })
32
+ .select();
33
+
34
+ if (error) throw error;
35
+
36
+ console.log('✅ Page saved to Supabase:', data);
37
+ return { success: true, data: data, error: null };
38
+ } catch (error) {
39
+ console.error('❌ Error saving page:', error);
40
+ return { success: false, data: null, error: error.message };
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Supabaseから全ページを取得
46
+ * @returns {Promise<{success: boolean, data: any[], error: string}>}
47
+ */
48
+ async function loadPagesFromSupabase() {
49
+ try {
50
+ const { data, error } = await supabaseClient
51
+ .from('page_builder_pages')
52
+ .select('*')
53
+ .order('updated_at', { ascending: false });
54
+
55
+ if (error) throw error;
56
+
57
+ console.log('✅ Pages loaded from Supabase:', data.length);
58
+ return { success: true, data: data, error: null };
59
+ } catch (error) {
60
+ console.error('❌ Error loading pages:', error);
61
+ return { success: false, data: [], error: error.message };
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Supabaseから特定のページを取得
67
+ * @param {string} pageId - ページID
68
+ * @returns {Promise<{success: boolean, data: any, error: string}>}
69
+ */
70
+ async function loadPageFromSupabase(pageId) {
71
+ try {
72
+ const { data, error } = await supabaseClient
73
+ .from('page_builder_pages')
74
+ .select('*')
75
+ .eq('id', pageId)
76
+ .single();
77
+
78
+ if (error) throw error;
79
+
80
+ console.log('✅ Page loaded from Supabase:', data.name);
81
+ return { success: true, data: data, error: null };
82
+ } catch (error) {
83
+ console.error('❌ Error loading page:', error);
84
+ return { success: false, data: null, error: error.message };
85
+ }
86
+ }
87
+
88
+ /**
89
+ * API実行ログをSupabaseに保存
90
+ * @param {string} apiName - API名
91
+ * @param {string} endpoint - エンドポイント
92
+ * @param {object} request - リクエストデータ
93
+ * @param {object} response - レスポンスデータ
94
+ * @param {string} status - ステータス ('success' or 'error')
95
+ * @returns {Promise<{success: boolean, data: any, error: string}>}
96
+ */
97
+ async function saveApiLogToSupabase(apiName, endpoint, request, response, status) {
98
+ try {
99
+ const { data, error } = await supabaseClient
100
+ .from('api_execution_logs')
101
+ .insert({
102
+ api_name: apiName,
103
+ endpoint: endpoint,
104
+ request_data: request,
105
+ response_data: response,
106
+ status: status,
107
+ executed_at: new Date().toISOString()
108
+ })
109
+ .select();
110
+
111
+ if (error) throw error;
112
+
113
+ console.log('✅ API log saved:', apiName, status);
114
+ return { success: true, data: data, error: null };
115
+ } catch (error) {
116
+ console.error('❌ Error saving API log:', error);
117
+ return { success: false, data: null, error: error.message };
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Supabaseから API実行ログを取得
123
+ * @param {number} limit - 取得件数
124
+ * @returns {Promise<{success: boolean, data: any[], error: string}>}
125
+ */
126
+ async function getApiLogsFromSupabase(limit = 50) {
127
+ try {
128
+ const { data, error } = await supabaseClient
129
+ .from('api_execution_logs')
130
+ .select('*')
131
+ .order('executed_at', { ascending: false })
132
+ .limit(limit);
133
+
134
+ if (error) throw error;
135
+
136
+ console.log('✅ API logs loaded:', data.length);
137
+ return { success: true, data: data, error: null };
138
+ } catch (error) {
139
+ console.error('❌ Error loading API logs:', error);
140
+ return { success: false, data: [], error: error.message };
141
+ }
142
+ }
143
+
144
+ // グローバルに公開
145
+ window.savePageToSupabase = savePageToSupabase;
146
+ window.loadPagesFromSupabase = loadPagesFromSupabase;
147
+ window.loadPageFromSupabase = loadPageFromSupabase;
148
+ window.saveApiLogToSupabase = saveApiLogToSupabase;
149
+ window.getApiLogsFromSupabase = getApiLogsFromSupabase;
workflows/.gitkeep ADDED
File without changes