careerrag 1.0.0__py3-none-any.whl
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.
- careerrag/__init__.py +1 -0
- careerrag/__main__.py +93 -0
- careerrag/config.py +37 -0
- careerrag/frontend/static/css/chat.css +443 -0
- careerrag/frontend/static/js/initialize-chat.js +320 -0
- careerrag/frontend/templates/chat.html +105 -0
- careerrag/rag/__init__.py +1 -0
- careerrag/rag/chunker.py +144 -0
- careerrag/rag/fusion.py +30 -0
- careerrag/rag/generator.py +67 -0
- careerrag/rag/indexer.py +47 -0
- careerrag/rag/keyword.py +35 -0
- careerrag/rag/loader.py +75 -0
- careerrag/rag/pipeline.py +46 -0
- careerrag/rag/prompt.py +50 -0
- careerrag/rag/reranker.py +26 -0
- careerrag/rag/retriever.py +73 -0
- careerrag/rag/selector.py +59 -0
- careerrag/rag/util.py +62 -0
- careerrag/rag/vector.py +29 -0
- careerrag/server/__init__.py +1 -0
- careerrag/server/app.py +68 -0
- careerrag-1.0.0.dist-info/METADATA +210 -0
- careerrag-1.0.0.dist-info/RECORD +27 -0
- careerrag-1.0.0.dist-info/WHEEL +4 -0
- careerrag-1.0.0.dist-info/entry_points.txt +2 -0
- careerrag-1.0.0.dist-info/licenses/LICENSE +21 -0
careerrag/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Provide a RAG-powered chat interface for professional documents."""
|
careerrag/__main__.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Run the CareerRAG command-line interface."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import chromadb
|
|
7
|
+
import typer
|
|
8
|
+
import uvicorn
|
|
9
|
+
|
|
10
|
+
from careerrag.config import DEFAULT_CONFIG, load_config, save_config
|
|
11
|
+
from careerrag.rag.chunker import chunk_document
|
|
12
|
+
from careerrag.rag.indexer import get_or_create_collection, index_chunks
|
|
13
|
+
from careerrag.rag.loader import load_document
|
|
14
|
+
from careerrag.rag.pipeline import stream_response
|
|
15
|
+
from careerrag.server.app import ServerConfig, create_app
|
|
16
|
+
|
|
17
|
+
SUPPORTED_EXTENSIONS = {".docx", ".md", ".pdf", ".txt"}
|
|
18
|
+
|
|
19
|
+
cli = typer.Typer(help="RAG-powered chat interface for career profiles")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cli.command()
|
|
23
|
+
def init() -> None:
|
|
24
|
+
"""Create a default configuration file."""
|
|
25
|
+
save_config(config=DEFAULT_CONFIG)
|
|
26
|
+
typer.echo(
|
|
27
|
+
"Created .careerrag/config.yml. Review settings before running other commands."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _index_documents(docs_path: Path, config: dict[str, object]) -> chromadb.Collection:
|
|
32
|
+
store = str(config["store"])
|
|
33
|
+
collection = get_or_create_collection(path=store)
|
|
34
|
+
count = 0
|
|
35
|
+
for file_path in sorted(docs_path.iterdir()):
|
|
36
|
+
if file_path.suffix.lower() not in SUPPORTED_EXTENSIONS:
|
|
37
|
+
continue
|
|
38
|
+
document = load_document(path=file_path)
|
|
39
|
+
chunks = chunk_document(document=document)
|
|
40
|
+
count += index_chunks(collection=collection, chunks=chunks)
|
|
41
|
+
typer.echo(f"Indexed {count} chunks from {docs_path}.")
|
|
42
|
+
return collection
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@cli.command()
|
|
46
|
+
def index(
|
|
47
|
+
docs: Path = typer.Option(..., help="Path to the documents directory"),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Index documents into the vector store."""
|
|
50
|
+
config = load_config()
|
|
51
|
+
_index_documents(docs_path=docs, config=config)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@cli.command()
|
|
55
|
+
def query(
|
|
56
|
+
question: str = typer.Option(..., help="Question to ask"),
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Answer a question from indexed documents."""
|
|
59
|
+
config = load_config()
|
|
60
|
+
store = str(config["store"])
|
|
61
|
+
collection = get_or_create_collection(path=store)
|
|
62
|
+
|
|
63
|
+
async def _run() -> None:
|
|
64
|
+
async for token in stream_response(collection=collection, question=question):
|
|
65
|
+
typer.echo(token, nl=False)
|
|
66
|
+
typer.echo()
|
|
67
|
+
|
|
68
|
+
asyncio.run(_run())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@cli.command()
|
|
72
|
+
def serve(
|
|
73
|
+
docs: Path | None = typer.Option(None, help="Path to the documents directory"),
|
|
74
|
+
name: str = typer.Option(..., help="Name to display in the chat UI"),
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Start the web server, indexing documents if provided."""
|
|
77
|
+
config = load_config()
|
|
78
|
+
store = str(config["store"])
|
|
79
|
+
collection = get_or_create_collection(path=store)
|
|
80
|
+
if docs:
|
|
81
|
+
_index_documents(docs_path=docs, config=config)
|
|
82
|
+
if collection.count() == 0:
|
|
83
|
+
typer.echo("No documents indexed. Pass --docs <directory> to index documents.")
|
|
84
|
+
raise typer.Exit(code=1)
|
|
85
|
+
server_config = ServerConfig(collection=collection, name=name)
|
|
86
|
+
host = str(config["host"])
|
|
87
|
+
port = int(config["port"])
|
|
88
|
+
web_app = create_app(config=server_config)
|
|
89
|
+
uvicorn.run(app=web_app, host=host, port=port)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
cli()
|
careerrag/config.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Load and save application configuration."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
CONFIG_DIR = Path(".careerrag")
|
|
9
|
+
CONFIG_FILE = CONFIG_DIR / "config.yml"
|
|
10
|
+
DEFAULT_CONFIG = {
|
|
11
|
+
"diversity_enabled": True,
|
|
12
|
+
"host": "127.0.0.1",
|
|
13
|
+
"keyword_enabled": True,
|
|
14
|
+
"model": "llama3.2",
|
|
15
|
+
"ollama_url": "http://localhost:11434/api/chat",
|
|
16
|
+
"port": 8000,
|
|
17
|
+
"provider": "ollama",
|
|
18
|
+
"rerank_enabled": False,
|
|
19
|
+
"store": ".careerrag/store",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def save_config(config: dict[str, Any]) -> None:
|
|
24
|
+
"""Write the configuration to disk."""
|
|
25
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
CONFIG_FILE.write_text(
|
|
27
|
+
yaml.dump(data=config, default_flow_style=False, sort_keys=True),
|
|
28
|
+
encoding="utf-8",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_config() -> dict[str, Any]:
|
|
33
|
+
"""Return the configuration with defaults for any missing keys."""
|
|
34
|
+
if not CONFIG_FILE.exists():
|
|
35
|
+
raise FileNotFoundError("Configuration not found. Run 'careerrag init'.")
|
|
36
|
+
user_config = yaml.safe_load(CONFIG_FILE.read_text(encoding="utf-8")) or {}
|
|
37
|
+
return {**DEFAULT_CONFIG, **user_config}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--accent: oklch(0.62 0.13 40);
|
|
3
|
+
--accent-ink: oklch(0.45 0.13 40);
|
|
4
|
+
--bubble-user: #ece6d9;
|
|
5
|
+
--column-max: min(720px, 92vw);
|
|
6
|
+
--composer-bg: #fffdf7;
|
|
7
|
+
--danger: #c0392b;
|
|
8
|
+
--duration-expand: 1000ms;
|
|
9
|
+
--duration-medium: 0.2s;
|
|
10
|
+
--duration-quick: 0.15s;
|
|
11
|
+
--ease: cubic-bezier(0.22, 0.8, 0.25, 1);
|
|
12
|
+
--fs-base: clamp(14px, 0.9vw + 12px, 16px);
|
|
13
|
+
--fs-hero: clamp(22px, 3.4vw, 34px);
|
|
14
|
+
--fs-message: clamp(14.5px, 0.5vw + 13px, 16px);
|
|
15
|
+
--gap-column: 0.5em;
|
|
16
|
+
--gap-composer: 0.65em;
|
|
17
|
+
--gap-turn: 0.4em;
|
|
18
|
+
--gutter: clamp(16px, 4vw, 28px);
|
|
19
|
+
--ink: #1f1c17;
|
|
20
|
+
--ink-2: #3a362e;
|
|
21
|
+
--lh-body: 1.55;
|
|
22
|
+
--muted: #6d675c;
|
|
23
|
+
--muted-2: #8e887c;
|
|
24
|
+
--pad-bubble: 0.65em;
|
|
25
|
+
--pad-composer: 1.4em;
|
|
26
|
+
--pad-composer-side: 1.1em;
|
|
27
|
+
--pad-message: 0.4em;
|
|
28
|
+
--pad-user-side: 0.9em;
|
|
29
|
+
--paper: #fbf8f1;
|
|
30
|
+
--radius-bubble: clamp(12px, 1.2vw, 18px);
|
|
31
|
+
--radius-bubble-corner: calc(var(--radius-bubble) * 0.25);
|
|
32
|
+
--radius-composer: clamp(18px, 2vw, 26px);
|
|
33
|
+
--radius-pill: 999px;
|
|
34
|
+
--rule: #e4ddcc;
|
|
35
|
+
--rule-strong: #d4ccb7;
|
|
36
|
+
--sans:
|
|
37
|
+
'Geist', ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
|
|
38
|
+
--scrollbar-thumb: #d8d0bf;
|
|
39
|
+
--scrollbar-thumb-hover: #c5bba3;
|
|
40
|
+
--serif: 'Instrument Serif', 'Iowan Old Style', Georgia, serif;
|
|
41
|
+
--size-button: 2.4em;
|
|
42
|
+
--spacing-list: 0.25em;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
* {
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
html,
|
|
50
|
+
body {
|
|
51
|
+
background: var(--paper);
|
|
52
|
+
color: var(--ink);
|
|
53
|
+
font-family: var(--sans);
|
|
54
|
+
font-size: var(--fs-base);
|
|
55
|
+
height: 100%;
|
|
56
|
+
margin: 0;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.cr-control {
|
|
61
|
+
color: inherit;
|
|
62
|
+
font-family: inherit;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.cr-root {
|
|
66
|
+
background: var(--paper);
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
inset: 0;
|
|
70
|
+
position: fixed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.cr-stage {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex: 1;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
min-height: 0;
|
|
78
|
+
position: relative;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.cr-empty {
|
|
82
|
+
inset: 0;
|
|
83
|
+
pointer-events: none;
|
|
84
|
+
position: absolute;
|
|
85
|
+
transform-origin: center center;
|
|
86
|
+
transition:
|
|
87
|
+
filter var(--duration-expand) var(--ease),
|
|
88
|
+
opacity var(--duration-expand) var(--ease),
|
|
89
|
+
transform var(--duration-expand) var(--ease);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.cr-empty-above {
|
|
93
|
+
align-items: center;
|
|
94
|
+
bottom: 50%;
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
gap: var(--gap-column);
|
|
98
|
+
left: 0;
|
|
99
|
+
margin-inline: auto;
|
|
100
|
+
max-width: var(--column-max);
|
|
101
|
+
padding: 0 var(--gutter);
|
|
102
|
+
padding-bottom: calc(
|
|
103
|
+
(var(--composer-empty-h, 70px) / 2) + clamp(28px, 5vh, 52px)
|
|
104
|
+
);
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
position: absolute;
|
|
107
|
+
right: 0;
|
|
108
|
+
text-align: center;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.cr-hero {
|
|
112
|
+
color: var(--ink);
|
|
113
|
+
font-family: var(--serif);
|
|
114
|
+
font-size: var(--fs-hero);
|
|
115
|
+
letter-spacing: -0.005em;
|
|
116
|
+
line-height: 1.15;
|
|
117
|
+
margin: 0;
|
|
118
|
+
max-width: 18ch;
|
|
119
|
+
text-wrap: balance;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.cr-hero-accent {
|
|
123
|
+
color: var(--accent);
|
|
124
|
+
font-style: italic;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.cr-chat {
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
inset: 0;
|
|
131
|
+
position: absolute;
|
|
132
|
+
transition:
|
|
133
|
+
opacity var(--duration-expand) var(--ease),
|
|
134
|
+
transform var(--duration-expand) var(--ease);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.cr-empty,
|
|
138
|
+
.cr-chat {
|
|
139
|
+
will-change: opacity, transform, filter;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.cr-stage[data-state='empty'] .cr-chat {
|
|
143
|
+
opacity: 0;
|
|
144
|
+
pointer-events: none;
|
|
145
|
+
transform: scale(0.985);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.cr-stage[data-state='leaving'] .cr-chat,
|
|
149
|
+
.cr-stage[data-state='chat'] .cr-chat {
|
|
150
|
+
opacity: 1;
|
|
151
|
+
pointer-events: auto;
|
|
152
|
+
transform: scale(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.cr-stage[data-state='leaving'] .cr-empty,
|
|
156
|
+
.cr-stage[data-state='chat'] .cr-empty {
|
|
157
|
+
filter: blur(6px);
|
|
158
|
+
opacity: 0;
|
|
159
|
+
pointer-events: none;
|
|
160
|
+
transform: scale(1.06);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.cr-stage[data-state='chat'] .cr-empty {
|
|
164
|
+
visibility: hidden;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.cr-scroll {
|
|
168
|
+
flex: 1;
|
|
169
|
+
min-height: 0;
|
|
170
|
+
overflow-anchor: none;
|
|
171
|
+
overflow-x: hidden;
|
|
172
|
+
overflow-y: auto;
|
|
173
|
+
scrollbar-color: var(--scrollbar-thumb) transparent;
|
|
174
|
+
scrollbar-width: thin;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.cr-scroll::-webkit-scrollbar {
|
|
178
|
+
width: 10px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.cr-scroll::-webkit-scrollbar-thumb {
|
|
182
|
+
background: var(--scrollbar-thumb);
|
|
183
|
+
border: 2px solid var(--paper);
|
|
184
|
+
border-radius: var(--radius-pill);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.cr-scroll::-webkit-scrollbar-thumb:hover {
|
|
188
|
+
background: var(--scrollbar-thumb-hover);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.cr-column {
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-direction: column;
|
|
194
|
+
gap: var(--gap-column);
|
|
195
|
+
margin: 0 auto;
|
|
196
|
+
max-width: var(--column-max);
|
|
197
|
+
overflow-anchor: none;
|
|
198
|
+
padding: 0 var(--gutter) var(--composer-pad, 120px);
|
|
199
|
+
width: 100%;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.cr-turn {
|
|
203
|
+
display: flex;
|
|
204
|
+
flex-direction: column;
|
|
205
|
+
gap: var(--gap-turn);
|
|
206
|
+
overflow-anchor: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.cr-turn + .cr-turn {
|
|
210
|
+
margin-top: clamp(14px, 2vh, 22px);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.cr-bottom-spacer {
|
|
214
|
+
flex: none;
|
|
215
|
+
height: 0;
|
|
216
|
+
overflow-anchor: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@keyframes turnIn {
|
|
220
|
+
from {
|
|
221
|
+
filter: blur(2px);
|
|
222
|
+
opacity: 0;
|
|
223
|
+
transform: translateY(40px);
|
|
224
|
+
}
|
|
225
|
+
to {
|
|
226
|
+
filter: none;
|
|
227
|
+
opacity: 1;
|
|
228
|
+
transform: none;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.cr-turn.entering {
|
|
233
|
+
animation: turnIn 720ms var(--ease) both;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.cr-message {
|
|
237
|
+
display: flex;
|
|
238
|
+
font-size: var(--fs-message);
|
|
239
|
+
overflow-anchor: none;
|
|
240
|
+
padding: var(--pad-message) 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.cr-message.assistant .cr-body {
|
|
244
|
+
color: var(--ink-2);
|
|
245
|
+
max-width: 100%;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.cr-message.user {
|
|
249
|
+
justify-content: flex-end;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.cr-message.user .cr-body {
|
|
253
|
+
background: var(--bubble-user);
|
|
254
|
+
border-radius: var(--radius-bubble) var(--radius-bubble)
|
|
255
|
+
var(--radius-bubble-corner) var(--radius-bubble);
|
|
256
|
+
color: var(--ink);
|
|
257
|
+
max-width: 85%;
|
|
258
|
+
padding: var(--pad-bubble) var(--pad-user-side);
|
|
259
|
+
white-space: pre-wrap;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.cr-message .cr-paragraph {
|
|
263
|
+
line-height: var(--lh-body);
|
|
264
|
+
margin: 0 0 var(--pad-bubble);
|
|
265
|
+
text-wrap: pretty;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.cr-message .cr-paragraph:last-child {
|
|
269
|
+
margin-bottom: 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.cr-message .cr-list {
|
|
273
|
+
margin: var(--spacing-list) 0 var(--pad-bubble);
|
|
274
|
+
padding-left: 1.25em;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.cr-message .cr-list-item {
|
|
278
|
+
color: var(--ink-2);
|
|
279
|
+
line-height: var(--lh-body);
|
|
280
|
+
margin-bottom: var(--gap-turn);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@keyframes cr-blink {
|
|
284
|
+
50% {
|
|
285
|
+
opacity: 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.cr-cursor {
|
|
290
|
+
animation: cr-blink 1.4s steps(2) infinite;
|
|
291
|
+
background: var(--accent);
|
|
292
|
+
border-radius: 1px;
|
|
293
|
+
display: inline-block;
|
|
294
|
+
height: 1em;
|
|
295
|
+
margin-left: 0.15em;
|
|
296
|
+
vertical-align: -0.1em;
|
|
297
|
+
width: 0.45em;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.cr-composer-wrap {
|
|
301
|
+
background: linear-gradient(
|
|
302
|
+
to top,
|
|
303
|
+
var(--paper) 60%,
|
|
304
|
+
color-mix(in srgb, var(--paper) 80%, transparent) 90%,
|
|
305
|
+
transparent
|
|
306
|
+
);
|
|
307
|
+
bottom: 0;
|
|
308
|
+
left: 0;
|
|
309
|
+
padding: clamp(14px, 2.4vh, 26px) var(--gutter) clamp(16px, 3vh, 30px);
|
|
310
|
+
pointer-events: none;
|
|
311
|
+
position: absolute;
|
|
312
|
+
right: 0;
|
|
313
|
+
transition:
|
|
314
|
+
bottom var(--duration-expand) var(--ease),
|
|
315
|
+
top var(--duration-expand) var(--ease),
|
|
316
|
+
transform var(--duration-expand) var(--ease);
|
|
317
|
+
z-index: 2;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.cr-stage[data-state='empty'] .cr-composer-wrap {
|
|
321
|
+
background: transparent;
|
|
322
|
+
bottom: auto;
|
|
323
|
+
top: 50%;
|
|
324
|
+
transform: translateY(-50%);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.cr-composer-inner {
|
|
328
|
+
margin: 0 auto;
|
|
329
|
+
max-width: var(--column-max);
|
|
330
|
+
pointer-events: auto;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.cr-composer {
|
|
334
|
+
align-items: flex-end;
|
|
335
|
+
background: var(--composer-bg);
|
|
336
|
+
border: 1px solid var(--rule-strong);
|
|
337
|
+
border-radius: var(--radius-composer);
|
|
338
|
+
box-shadow:
|
|
339
|
+
0 12px 36px -18px rgba(40, 30, 20, 0.25),
|
|
340
|
+
0 2px 8px rgba(40, 30, 20, 0.05);
|
|
341
|
+
display: flex;
|
|
342
|
+
gap: var(--gap-composer);
|
|
343
|
+
padding: var(--pad-composer) var(--pad-composer-side) var(--pad-composer-side)
|
|
344
|
+
var(--pad-composer);
|
|
345
|
+
transition:
|
|
346
|
+
border-color var(--duration-quick),
|
|
347
|
+
box-shadow var(--duration-quick);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.cr-composer:focus-within {
|
|
351
|
+
border-color: var(--accent);
|
|
352
|
+
box-shadow:
|
|
353
|
+
0 0 0 3px color-mix(in oklch, var(--accent) 18%, transparent),
|
|
354
|
+
0 12px 36px -18px rgba(40, 30, 20, 0.22);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.cr-input {
|
|
358
|
+
background: transparent;
|
|
359
|
+
border: 0;
|
|
360
|
+
color: var(--ink);
|
|
361
|
+
flex: 1;
|
|
362
|
+
font-size: var(--fs-message);
|
|
363
|
+
line-height: 1.6;
|
|
364
|
+
max-height: 12.5em;
|
|
365
|
+
min-height: 2.6em;
|
|
366
|
+
outline: 0;
|
|
367
|
+
padding: var(--pad-message) 0;
|
|
368
|
+
resize: none;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.cr-input::placeholder {
|
|
372
|
+
color: var(--muted-2);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.cr-reset {
|
|
376
|
+
align-self: flex-end;
|
|
377
|
+
background: transparent;
|
|
378
|
+
border: 1px solid var(--rule);
|
|
379
|
+
border-radius: var(--radius-pill);
|
|
380
|
+
color: var(--muted);
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
display: grid;
|
|
383
|
+
flex: none;
|
|
384
|
+
height: var(--size-button);
|
|
385
|
+
place-items: center;
|
|
386
|
+
transition:
|
|
387
|
+
background var(--duration-quick),
|
|
388
|
+
border-color var(--duration-quick),
|
|
389
|
+
color var(--duration-quick),
|
|
390
|
+
opacity var(--duration-medium) var(--ease),
|
|
391
|
+
width var(--duration-medium) var(--ease);
|
|
392
|
+
width: var(--size-button);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.cr-reset:hover {
|
|
396
|
+
background: color-mix(in srgb, var(--danger) 8%, var(--composer-bg));
|
|
397
|
+
border-color: var(--danger);
|
|
398
|
+
color: var(--danger);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.cr-stage[data-state='empty'] .cr-reset {
|
|
402
|
+
border-width: 0;
|
|
403
|
+
margin: 0;
|
|
404
|
+
opacity: 0;
|
|
405
|
+
overflow: hidden;
|
|
406
|
+
padding: 0;
|
|
407
|
+
pointer-events: none;
|
|
408
|
+
width: 0;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.cr-send {
|
|
412
|
+
background: var(--ink);
|
|
413
|
+
border: 0;
|
|
414
|
+
border-radius: var(--radius-pill);
|
|
415
|
+
color: var(--paper);
|
|
416
|
+
cursor: pointer;
|
|
417
|
+
display: grid;
|
|
418
|
+
flex: none;
|
|
419
|
+
height: var(--size-button);
|
|
420
|
+
place-items: center;
|
|
421
|
+
transition:
|
|
422
|
+
background var(--duration-quick),
|
|
423
|
+
opacity var(--duration-quick);
|
|
424
|
+
width: var(--size-button);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.cr-send:disabled {
|
|
428
|
+
background: var(--muted-2);
|
|
429
|
+
cursor: default;
|
|
430
|
+
opacity: 0.45;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.cr-send:hover:not(:disabled) {
|
|
434
|
+
background: var(--accent-ink);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.cr-send.armed {
|
|
438
|
+
background: var(--accent);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.cr-send.armed:hover {
|
|
442
|
+
background: var(--accent-ink);
|
|
443
|
+
}
|