Spaces:
Paused
Paused
frdel commited on
Commit ·
215dbf1
1
Parent(s): 6ff9f06
Scheduler polishing 1
Browse files- agent.py +6 -10
- docker/run/Dockerfile +0 -3
- docker/run/DockerfileKali +0 -3
- docker/run/fs/etc/cron.d/scheduler_tick +0 -5
- docker/run/fs/ins/pre_install.sh +3 -0
- python/api/poll.py +1 -1
- python/extensions/message_loop_prompts/_60_include_current_datetime.py +8 -7
- python/extensions/system_prompt/_10_system_prompt.py +4 -19
- python/helpers/job_loop.py +20 -0
- python/tools/input.py +1 -1
- run_ui.py +21 -4
- test_scheduler.py +0 -16
- webui/css/settings.css +5 -5
- webui/index.css +3 -3
agent.py
CHANGED
|
@@ -163,10 +163,10 @@ class AgentContext:
|
|
| 163 |
tool_name="call_subordinate", tool_result=msg # type: ignore
|
| 164 |
)
|
| 165 |
)
|
| 166 |
-
response = await agent.monologue()
|
| 167 |
superior = agent.data.get(Agent.DATA_NAME_SUPERIOR, None)
|
| 168 |
if superior:
|
| 169 |
-
response = await self._process_chain(superior, response, False)
|
| 170 |
return response
|
| 171 |
except Exception as e:
|
| 172 |
agent.handle_critical_exception(e)
|
|
@@ -313,7 +313,7 @@ class Agent:
|
|
| 313 |
|
| 314 |
agent_response = await self.call_chat_model(
|
| 315 |
prompt, callback=stream_callback
|
| 316 |
-
)
|
| 317 |
|
| 318 |
await self.handle_intervention(agent_response)
|
| 319 |
|
|
@@ -499,13 +499,9 @@ class Agent:
|
|
| 499 |
system_message=message.system_message
|
| 500 |
)
|
| 501 |
|
| 502 |
-
# remove empty
|
| 503 |
-
if (
|
| 504 |
-
|
| 505 |
-
and "attachments" in content
|
| 506 |
-
and not content["attachments"]
|
| 507 |
-
):
|
| 508 |
-
del content["attachments"]
|
| 509 |
|
| 510 |
# add to history
|
| 511 |
msg = self.hist_add_message(False, content=content) # type: ignore
|
|
|
|
| 163 |
tool_name="call_subordinate", tool_result=msg # type: ignore
|
| 164 |
)
|
| 165 |
)
|
| 166 |
+
response = await agent.monologue() # type: ignore
|
| 167 |
superior = agent.data.get(Agent.DATA_NAME_SUPERIOR, None)
|
| 168 |
if superior:
|
| 169 |
+
response = await self._process_chain(superior, response, False) # type: ignore
|
| 170 |
return response
|
| 171 |
except Exception as e:
|
| 172 |
agent.handle_critical_exception(e)
|
|
|
|
| 313 |
|
| 314 |
agent_response = await self.call_chat_model(
|
| 315 |
prompt, callback=stream_callback
|
| 316 |
+
) # type: ignore
|
| 317 |
|
| 318 |
await self.handle_intervention(agent_response)
|
| 319 |
|
|
|
|
| 499 |
system_message=message.system_message
|
| 500 |
)
|
| 501 |
|
| 502 |
+
# remove empty parts from template
|
| 503 |
+
if isinstance(content, dict):
|
| 504 |
+
content = {k: v for k, v in content.items() if v}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
|
| 506 |
# add to history
|
| 507 |
msg = self.hist_add_message(False, content=content) # type: ignore
|
docker/run/Dockerfile
CHANGED
|
@@ -43,8 +43,5 @@ EXPOSE 22 80
|
|
| 43 |
|
| 44 |
RUN chmod +x /exe/initialize.sh /exe/run_A0.sh /exe/run_searxng.sh
|
| 45 |
|
| 46 |
-
# This is important or cron will not execute the file
|
| 47 |
-
RUN chmod 0644 /etc/cron.d/scheduler_tick
|
| 48 |
-
|
| 49 |
# initialize runtime and switch to supervisord
|
| 50 |
CMD ["/exe/initialize.sh", "$BRANCH"]
|
|
|
|
| 43 |
|
| 44 |
RUN chmod +x /exe/initialize.sh /exe/run_A0.sh /exe/run_searxng.sh
|
| 45 |
|
|
|
|
|
|
|
|
|
|
| 46 |
# initialize runtime and switch to supervisord
|
| 47 |
CMD ["/exe/initialize.sh", "$BRANCH"]
|
docker/run/DockerfileKali
CHANGED
|
@@ -44,8 +44,5 @@ EXPOSE 22 80
|
|
| 44 |
|
| 45 |
RUN chmod +x /exe/initialize.sh /exe/run_A0.sh /exe/run_searxng.sh
|
| 46 |
|
| 47 |
-
# This is important or cron will not execute the file
|
| 48 |
-
RUN chmod 0644 /etc/cron.d/scheduler_tick
|
| 49 |
-
|
| 50 |
# initialize runtime and switch to supervisord
|
| 51 |
CMD ["/exe/initialize.sh", "$BRANCH"]
|
|
|
|
| 44 |
|
| 45 |
RUN chmod +x /exe/initialize.sh /exe/run_A0.sh /exe/run_searxng.sh
|
| 46 |
|
|
|
|
|
|
|
|
|
|
| 47 |
# initialize runtime and switch to supervisord
|
| 48 |
CMD ["/exe/initialize.sh", "$BRANCH"]
|
docker/run/fs/etc/cron.d/scheduler_tick
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
# TaskScheduler CronJob
|
| 2 |
-
|
| 3 |
-
* * * * * root curl http://127.0.0.1:80/scheduler_tick
|
| 4 |
-
|
| 5 |
-
# END
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docker/run/fs/ins/pre_install.sh
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
#!/bin/bash
|
| 2 |
|
|
|
|
|
|
|
|
|
|
| 3 |
# Update and install necessary packages
|
| 4 |
apt-get update && apt-get install -y \
|
| 5 |
python3 \
|
|
|
|
| 1 |
#!/bin/bash
|
| 2 |
|
| 3 |
+
# fix permissions for cron files
|
| 4 |
+
chmod 0644 /etc/cron.d/*
|
| 5 |
+
|
| 6 |
# Update and install necessary packages
|
| 7 |
apt-get update && apt-get install -y \
|
| 8 |
python3 \
|
python/api/poll.py
CHANGED
|
@@ -32,7 +32,7 @@ class Poll(ApiHandler):
|
|
| 32 |
scheduler = TaskScheduler.get()
|
| 33 |
|
| 34 |
# Always reload the scheduler on each poll to ensure we have the latest task state
|
| 35 |
-
await scheduler.reload()
|
| 36 |
|
| 37 |
# loop AgentContext._contexts and divide into contexts and tasks
|
| 38 |
|
|
|
|
| 32 |
scheduler = TaskScheduler.get()
|
| 33 |
|
| 34 |
# Always reload the scheduler on each poll to ensure we have the latest task state
|
| 35 |
+
# await scheduler.reload() # TODO:SCHEDULER must optimize this
|
| 36 |
|
| 37 |
# loop AgentContext._contexts and divide into contexts and tasks
|
| 38 |
|
python/extensions/message_loop_prompts/_60_include_current_datetime.py
CHANGED
|
@@ -7,16 +7,17 @@ from python.helpers.localization import Localization
|
|
| 7 |
class IncludeCurrentDatetime(Extension):
|
| 8 |
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
|
| 9 |
# get current datetime
|
| 10 |
-
current_datetime = Localization.get().utc_dt_to_localtime_str(
|
|
|
|
|
|
|
| 11 |
# remove timezone offset
|
| 12 |
-
current_datetime
|
| 13 |
-
|
| 14 |
-
# remove old current datetime from loop data
|
| 15 |
-
if "current_datetime" in loop_data.extras_temporary:
|
| 16 |
-
del loop_data.extras_temporary["current_datetime"]
|
| 17 |
|
| 18 |
# read prompt
|
| 19 |
-
datetime_prompt = self.agent.read_prompt(
|
|
|
|
|
|
|
| 20 |
|
| 21 |
# add current datetime to the loop data
|
| 22 |
loop_data.extras_temporary["current_datetime"] = datetime_prompt
|
|
|
|
| 7 |
class IncludeCurrentDatetime(Extension):
|
| 8 |
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
|
| 9 |
# get current datetime
|
| 10 |
+
current_datetime = Localization.get().utc_dt_to_localtime_str(
|
| 11 |
+
datetime.now(timezone.utc), sep=" ", timespec="seconds"
|
| 12 |
+
)
|
| 13 |
# remove timezone offset
|
| 14 |
+
if current_datetime and "+" in current_datetime:
|
| 15 |
+
current_datetime = current_datetime.split("+")[0]
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
# read prompt
|
| 18 |
+
datetime_prompt = self.agent.read_prompt(
|
| 19 |
+
"agent.system.datetime.md", date_time=current_datetime
|
| 20 |
+
)
|
| 21 |
|
| 22 |
# add current datetime to the loop data
|
| 23 |
loop_data.extras_temporary["current_datetime"] = datetime_prompt
|
python/extensions/system_prompt/_10_system_prompt.py
CHANGED
|
@@ -15,26 +15,11 @@ class SystemPrompt(Extension):
|
|
| 15 |
|
| 16 |
|
| 17 |
def get_main_prompt(agent: Agent):
|
| 18 |
-
return
|
| 19 |
|
| 20 |
|
| 21 |
def get_tools_prompt(agent: Agent):
|
| 22 |
-
prompt =
|
| 23 |
if agent.config.chat_model.vision:
|
| 24 |
-
prompt += '\n' +
|
| 25 |
-
return prompt
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
def get_prompt(file: str, agent: Agent):
|
| 29 |
-
# variables for system prompts
|
| 30 |
-
# TODO: move variables to the end of chain
|
| 31 |
-
# variables in system prompt would break prompt caching, better to add them to the last message in conversation
|
| 32 |
-
# get current datetime
|
| 33 |
-
current_datetime = Localization.get().utc_dt_to_localtime_str(datetime.now(timezone.utc), sep=" ", timespec="seconds")
|
| 34 |
-
# remove timezone offset
|
| 35 |
-
current_datetime = current_datetime.split("+")[0]
|
| 36 |
-
vars = {
|
| 37 |
-
"date_time": current_datetime,
|
| 38 |
-
"agent_name": agent.agent_name,
|
| 39 |
-
}
|
| 40 |
-
return agent.read_prompt(file, **vars)
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
def get_main_prompt(agent: Agent):
|
| 18 |
+
return agent.read_prompt("agent.system.main.md")
|
| 19 |
|
| 20 |
|
| 21 |
def get_tools_prompt(agent: Agent):
|
| 22 |
+
prompt = agent.read_prompt("agent.system.tools.md")
|
| 23 |
if agent.config.chat_model.vision:
|
| 24 |
+
prompt += '\n' + agent.read_prompt("agent.system.tools_vision.md")
|
| 25 |
+
return prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
python/helpers/job_loop.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
from python.helpers.task_scheduler import TaskScheduler
|
| 3 |
+
from python.helpers.print_style import PrintStyle
|
| 4 |
+
from python.helpers import errors
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
async def run_loop():
|
| 8 |
+
while True:
|
| 9 |
+
try:
|
| 10 |
+
await scheduler_tick()
|
| 11 |
+
except Exception as e:
|
| 12 |
+
PrintStyle().error(errors.format_error(e))
|
| 13 |
+
await asyncio.sleep(60)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
async def scheduler_tick():
|
| 17 |
+
# Get the task scheduler instance and print detailed debug info
|
| 18 |
+
scheduler = TaskScheduler.get()
|
| 19 |
+
# Run the scheduler tick
|
| 20 |
+
await scheduler.tick()
|
python/tools/input.py
CHANGED
|
@@ -12,7 +12,7 @@ class Input(Tool):
|
|
| 12 |
|
| 13 |
# forward keyboard input to code execution tool
|
| 14 |
args = {"runtime": "terminal", "code": keyboard}
|
| 15 |
-
cot = CodeExecution(self.agent, "code_execution_tool", args, self.message)
|
| 16 |
cot.log = self.log
|
| 17 |
return await cot.execute(**args)
|
| 18 |
|
|
|
|
| 12 |
|
| 13 |
# forward keyboard input to code execution tool
|
| 14 |
args = {"runtime": "terminal", "code": keyboard}
|
| 15 |
+
cot = CodeExecution(self.agent, "code_execution_tool", "", args, self.message)
|
| 16 |
cot.log = self.log
|
| 17 |
return await cot.execute(**args)
|
| 18 |
|
run_ui.py
CHANGED
|
@@ -15,11 +15,13 @@ from python.helpers import persist_chat, runtime, dotenv, process
|
|
| 15 |
from python.helpers.cloudflare_tunnel import CloudflareTunnel
|
| 16 |
from python.helpers.extract_tools import load_classes_from_folder
|
| 17 |
from python.helpers.api import ApiHandler
|
|
|
|
| 18 |
from python.helpers.print_style import PrintStyle
|
| 19 |
from python.helpers.task_scheduler import TaskScheduler
|
|
|
|
| 20 |
|
| 21 |
# Set the new timezone to 'UTC'
|
| 22 |
-
os.environ[
|
| 23 |
# Apply the timezone change
|
| 24 |
time.tzset()
|
| 25 |
|
|
@@ -35,8 +37,10 @@ basic_auth = BasicAuth(app)
|
|
| 35 |
|
| 36 |
def is_loopback_address(address):
|
| 37 |
loopback_checker = {
|
| 38 |
-
socket.AF_INET: lambda x: struct.unpack(
|
| 39 |
-
|
|
|
|
|
|
|
| 40 |
}
|
| 41 |
address_type = "hostname"
|
| 42 |
try:
|
|
@@ -79,6 +83,7 @@ def requires_api_key(f):
|
|
| 79 |
else:
|
| 80 |
return Response("API key required", 401)
|
| 81 |
return await f(*args, **kwargs)
|
|
|
|
| 82 |
return decorated
|
| 83 |
|
| 84 |
|
|
@@ -93,6 +98,7 @@ def requires_loopback(f):
|
|
| 93 |
{},
|
| 94 |
)
|
| 95 |
return await f(*args, **kwargs)
|
|
|
|
| 96 |
return decorated
|
| 97 |
|
| 98 |
|
|
@@ -142,6 +148,10 @@ def run():
|
|
| 142 |
from werkzeug.serving import WSGIRequestHandler
|
| 143 |
from werkzeug.serving import make_server
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
class NoRequestLoggingWSGIRequestHandler(WSGIRequestHandler):
|
| 146 |
def log_request(self, code="-", size="-"):
|
| 147 |
pass # Override to suppress request logging
|
|
@@ -188,17 +198,23 @@ def run():
|
|
| 188 |
instance = handler(app, lock)
|
| 189 |
|
| 190 |
if handler.requires_loopback():
|
|
|
|
| 191 |
@requires_loopback
|
| 192 |
async def handle_request():
|
| 193 |
return await instance.handle_request(request=request)
|
|
|
|
| 194 |
elif handler.requires_auth():
|
|
|
|
| 195 |
@requires_auth
|
| 196 |
async def handle_request():
|
| 197 |
return await instance.handle_request(request=request)
|
|
|
|
| 198 |
elif handler.requires_api_key():
|
|
|
|
| 199 |
@requires_api_key
|
| 200 |
async def handle_request():
|
| 201 |
return await instance.handle_request(request=request)
|
|
|
|
| 202 |
else:
|
| 203 |
# Fallback to requires_auth
|
| 204 |
@requires_auth
|
|
@@ -232,7 +248,8 @@ def run():
|
|
| 232 |
nonlocal tunnel, server, printer
|
| 233 |
with lock:
|
| 234 |
printer.print("Caught signal, stopping server...")
|
| 235 |
-
server
|
|
|
|
| 236 |
process.stop_server()
|
| 237 |
if tunnel:
|
| 238 |
tunnel.stop()
|
|
|
|
| 15 |
from python.helpers.cloudflare_tunnel import CloudflareTunnel
|
| 16 |
from python.helpers.extract_tools import load_classes_from_folder
|
| 17 |
from python.helpers.api import ApiHandler
|
| 18 |
+
from python.helpers.job_loop import run_loop
|
| 19 |
from python.helpers.print_style import PrintStyle
|
| 20 |
from python.helpers.task_scheduler import TaskScheduler
|
| 21 |
+
from python.helpers.defer import DeferredTask
|
| 22 |
|
| 23 |
# Set the new timezone to 'UTC'
|
| 24 |
+
os.environ["TZ"] = "UTC"
|
| 25 |
# Apply the timezone change
|
| 26 |
time.tzset()
|
| 27 |
|
|
|
|
| 37 |
|
| 38 |
def is_loopback_address(address):
|
| 39 |
loopback_checker = {
|
| 40 |
+
socket.AF_INET: lambda x: struct.unpack("!I", socket.inet_aton(x))[0]
|
| 41 |
+
>> (32 - 8)
|
| 42 |
+
== 127,
|
| 43 |
+
socket.AF_INET6: lambda x: x == "::1",
|
| 44 |
}
|
| 45 |
address_type = "hostname"
|
| 46 |
try:
|
|
|
|
| 83 |
else:
|
| 84 |
return Response("API key required", 401)
|
| 85 |
return await f(*args, **kwargs)
|
| 86 |
+
|
| 87 |
return decorated
|
| 88 |
|
| 89 |
|
|
|
|
| 98 |
{},
|
| 99 |
)
|
| 100 |
return await f(*args, **kwargs)
|
| 101 |
+
|
| 102 |
return decorated
|
| 103 |
|
| 104 |
|
|
|
|
| 148 |
from werkzeug.serving import WSGIRequestHandler
|
| 149 |
from werkzeug.serving import make_server
|
| 150 |
|
| 151 |
+
PrintStyle().print("Starting job loop...")
|
| 152 |
+
job_loop = DeferredTask().start_task(run_loop)
|
| 153 |
+
|
| 154 |
+
PrintStyle().print("Starting server...")
|
| 155 |
class NoRequestLoggingWSGIRequestHandler(WSGIRequestHandler):
|
| 156 |
def log_request(self, code="-", size="-"):
|
| 157 |
pass # Override to suppress request logging
|
|
|
|
| 198 |
instance = handler(app, lock)
|
| 199 |
|
| 200 |
if handler.requires_loopback():
|
| 201 |
+
|
| 202 |
@requires_loopback
|
| 203 |
async def handle_request():
|
| 204 |
return await instance.handle_request(request=request)
|
| 205 |
+
|
| 206 |
elif handler.requires_auth():
|
| 207 |
+
|
| 208 |
@requires_auth
|
| 209 |
async def handle_request():
|
| 210 |
return await instance.handle_request(request=request)
|
| 211 |
+
|
| 212 |
elif handler.requires_api_key():
|
| 213 |
+
|
| 214 |
@requires_api_key
|
| 215 |
async def handle_request():
|
| 216 |
return await instance.handle_request(request=request)
|
| 217 |
+
|
| 218 |
else:
|
| 219 |
# Fallback to requires_auth
|
| 220 |
@requires_auth
|
|
|
|
| 248 |
nonlocal tunnel, server, printer
|
| 249 |
with lock:
|
| 250 |
printer.print("Caught signal, stopping server...")
|
| 251 |
+
if server:
|
| 252 |
+
server.shutdown()
|
| 253 |
process.stop_server()
|
| 254 |
if tunnel:
|
| 255 |
tunnel.stop()
|
test_scheduler.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
from python.helpers.task_scheduler import ScheduledTask, TaskSchedule, SchedulerTaskList, TaskState
|
| 2 |
-
import asyncio
|
| 3 |
-
|
| 4 |
-
slist = SchedulerTaskList.get()
|
| 5 |
-
|
| 6 |
-
print(slist.model_dump_json(indent=4))
|
| 7 |
-
|
| 8 |
-
for task in slist.tasks:
|
| 9 |
-
t = slist.get_task_by_uuid(task.uuid)
|
| 10 |
-
t.update(state=TaskState.DISABLED)
|
| 11 |
-
print("-" * 100)
|
| 12 |
-
print(t.model_dump_json(indent=4))
|
| 13 |
-
|
| 14 |
-
print("-" * 100)
|
| 15 |
-
|
| 16 |
-
print(slist.model_dump_json(indent=4))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webui/css/settings.css
CHANGED
|
@@ -329,21 +329,21 @@ nav ul li a img {
|
|
| 329 |
|
| 330 |
.settings-tab.active {
|
| 331 |
border-color: var(--color-border);
|
| 332 |
-
box-shadow:
|
| 333 |
0 -4px 8px -2px var(--color-border),
|
| 334 |
4px 0 8px -2px var(--color-border),
|
| 335 |
-
-4px 0 8px -2px var(--color-border);
|
| 336 |
font-weight: bold;
|
| 337 |
background-color: var(--color-panel);
|
| 338 |
}
|
| 339 |
|
| 340 |
/* Light mode overrides */
|
| 341 |
.light-mode .settings-tab.active {
|
| 342 |
-
color: var(--color-border);
|
| 343 |
-
box-shadow:
|
| 344 |
0 -4px 8px -2px var(--color-border),
|
| 345 |
4px 0 8px -2px var(--color-border),
|
| 346 |
-
-4px 0 8px -2px var(--color-border);
|
| 347 |
}
|
| 348 |
|
| 349 |
.light-mode .settings-tab:not(.active) {
|
|
|
|
| 329 |
|
| 330 |
.settings-tab.active {
|
| 331 |
border-color: var(--color-border);
|
| 332 |
+
/* box-shadow:
|
| 333 |
0 -4px 8px -2px var(--color-border),
|
| 334 |
4px 0 8px -2px var(--color-border),
|
| 335 |
+
-4px 0 8px -2px var(--color-border); */
|
| 336 |
font-weight: bold;
|
| 337 |
background-color: var(--color-panel);
|
| 338 |
}
|
| 339 |
|
| 340 |
/* Light mode overrides */
|
| 341 |
.light-mode .settings-tab.active {
|
| 342 |
+
/* color: var(--color-border); */
|
| 343 |
+
/* box-shadow:
|
| 344 |
0 -4px 8px -2px var(--color-border),
|
| 345 |
4px 0 8px -2px var(--color-border),
|
| 346 |
+
-4px 0 8px -2px var(--color-border); */
|
| 347 |
}
|
| 348 |
|
| 349 |
.light-mode .settings-tab:not(.active) {
|
webui/index.css
CHANGED
|
@@ -2259,7 +2259,7 @@ a:active {
|
|
| 2259 |
|
| 2260 |
.light-mode .tab.active::after {
|
| 2261 |
background-color: var(--highlight-pink);
|
| 2262 |
-
box-shadow: 0 0 8px var(--highlight-pink);
|
| 2263 |
}
|
| 2264 |
/* Tabs styling */
|
| 2265 |
.tabs-container {
|
|
@@ -2300,10 +2300,10 @@ a:active {
|
|
| 2300 |
|
| 2301 |
.tab.active {
|
| 2302 |
border-color: var(--color-border);
|
| 2303 |
-
box-shadow:
|
| 2304 |
0 -4px 8px -2px var(--color-border),
|
| 2305 |
4px 0 8px -2px var(--color-border),
|
| 2306 |
-
-4px 0 8px -2px var(--color-border);
|
| 2307 |
font-weight: bold;
|
| 2308 |
background-color: var(--color-panel);
|
| 2309 |
}
|
|
|
|
| 2259 |
|
| 2260 |
.light-mode .tab.active::after {
|
| 2261 |
background-color: var(--highlight-pink);
|
| 2262 |
+
/* box-shadow: 0 0 8px var(--highlight-pink); */
|
| 2263 |
}
|
| 2264 |
/* Tabs styling */
|
| 2265 |
.tabs-container {
|
|
|
|
| 2300 |
|
| 2301 |
.tab.active {
|
| 2302 |
border-color: var(--color-border);
|
| 2303 |
+
/* box-shadow:
|
| 2304 |
0 -4px 8px -2px var(--color-border),
|
| 2305 |
4px 0 8px -2px var(--color-border),
|
| 2306 |
+
-4px 0 8px -2px var(--color-border); */
|
| 2307 |
font-weight: bold;
|
| 2308 |
background-color: var(--color-panel);
|
| 2309 |
}
|