File size: 6,576 Bytes
f866820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { useState } from 'react';
import { runQuery } from '../api/client';
import ResultCard from './ResultCard';

export default function QueryPanel({ accessToken }) {
  const [query, setQuery] = useState('');
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [showDebug, setShowDebug] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!query.trim()) return;

    if (!accessToken) {
      setError('Please connect to Dropbox first to enable zero-storage queries.');
      return;
    }

    setLoading(true);
    setError(null);
    try {
      const data = await runQuery(query, accessToken);
      setResult(data);
      if (data.error) {
        setError(data.error);
      }
    } catch (err) {
      setError(err.message);
    }
    setLoading(false);
  };

  const handleClear = () => {
    setQuery('');
    setResult(null);
    setError(null);
    setShowDebug(false);
  };

  return (
    <div className="flex-1 p-6 overflow-auto bg-slate-900">
      {/* Query Form */}
      <form onSubmit={handleSubmit} className="mb-6">
        <label className="block text-sm font-medium text-slate-300 mb-2">
          Enter your question:
        </label>
        <div className="flex gap-3">
          <input
            type="text"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="What would you like to know?"
            className="flex-1 px-4 py-3 border border-slate-600 rounded-lg bg-slate-800 text-slate-100 placeholder-slate-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 focus:ring-offset-slate-900 focus:border-blue-500 transition-all duration-200"
          />
          <button
            type="submit"
            disabled={loading || !query.trim()}
            className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed shadow-sm transition-all duration-200"
          >
            {loading ? (
              <span className="flex items-center gap-2">
                <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
                Searching...
              </span>
            ) : (
              'Search'
            )}
          </button>
        </div>
      </form>

      {/* Error Message */}
      {error && (
        <div
          className="bg-red-900/30 border border-red-700 text-red-400 px-4 py-3 rounded-lg mb-6 flex items-start gap-3"
          role="alert"
        >
          <svg className="w-5 h-5 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          <span>{error}</span>
        </div>
      )}

      {/* Results */}
      {result && !error && (
        <div className="space-y-6">
          {/* Results Header with Clear Button */}
          <div className="flex items-center justify-between">
            <h2 className="text-base font-semibold text-slate-100">Answer</h2>
            <button
              type="button"
              onClick={handleClear}
              className="flex items-center gap-1.5 text-sm text-slate-400 hover:text-slate-200 transition-colors"
            >
              <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
              </svg>
              Clear Results
            </button>
          </div>

          {/* Answer Card */}
          <div className="bg-slate-800 border border-slate-700 rounded-lg p-4 shadow-sm">
            <p className="text-slate-200 whitespace-pre-wrap leading-relaxed">
              {result.answer || 'No answer generated.'}
            </p>
          </div>

          {/* Citations */}
          {result.citations?.length > 0 && (
            <div>
              <h2 className="text-base font-semibold text-slate-100 mb-3">
                Citations ({result.citations.length})
              </h2>
              <div className="space-y-3">
                {result.citations.map((citation, idx) => (
                  <ResultCard key={citation.id || idx} citation={citation} />
                ))}
              </div>
            </div>
          )}

          {/* Debug Toggle */}
          <div className="pt-2">
            <button
              type="button"
              onClick={() => setShowDebug(!showDebug)}
              className="flex items-center gap-2 text-sm text-slate-400 hover:text-slate-200 transition-colors"
            >
              <svg
                className={`w-4 h-4 transition-transform duration-200 ${showDebug ? 'rotate-90' : ''}`}
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
              </svg>
              {showDebug ? 'Hide Debug Info' : 'Show Debug Info'}
            </button>
            {showDebug && (
              <pre className="mt-3 bg-slate-950 text-slate-300 p-4 rounded-lg text-xs overflow-auto max-h-96 border border-slate-700">
                {JSON.stringify(result, null, 2)}
              </pre>
            )}
          </div>
        </div>
      )}

      {/* Empty State - No query yet */}
      {!result && !error && !loading && (
        <div className="flex flex-col items-center justify-center py-16 text-center">
          <svg className="w-16 h-16 text-slate-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
          </svg>
          <h3 className="text-lg font-medium text-slate-300 mb-2">Ask a Question</h3>
          <p className="text-sm text-slate-500 max-w-sm">
            {accessToken
              ? 'Enter a question above to search your indexed documents. Results will include relevant citations from your files.'
              : 'Connect to Dropbox and index your files first, then ask questions here. Your documents are re-fetched at query time for true zero-storage privacy.'
            }
          </p>
        </div>
      )}
    </div>
  );
}