To solve this optimization problem, we first need to identify the decision variables, objective function, and constraints.

- Decision Variables:
  - Let \(T\) be the number of taro bubble teas made.
  - Let \(M\) be the number of mango bubble teas made.

- Objective Function:
  - The objective is to maximize profit. Given that the profit per taro bubble tea is $4 and per mango bubble tea is $6, the total profit can be represented as \(4T + 6M\).

- Constraints:
  - Taro availability: \(3T \leq 60\) (since one taro bubble tea requires 3 units of taro and there are 60 units available).
  - Mango availability: \(3M \leq 60\) (since one mango bubble tea requires 3 units of mango and there are 60 units available).
  - Milk availability: \(4T + 5M \leq 140\) (since one taro bubble tea requires 4 units of milk, one mango bubble tea requires 5 units of milk, and there are 140 units of milk available).
  - Non-negativity constraints: \(T \geq 0\) and \(M \geq 0\), since the shop cannot make a negative number of teas.

Now, let's translate these into Gurobi code in Python:

```python
from gurobipy import *

# Create a model
model = Model("Bubble_Tea_Optimization")

# Define variables
T = model.addVar(vtype=GRB.CONTINUOUS, name="taro_bubble_teas", lb=0)
M = model.addVar(vtype=GRB.CONTINUOUS, name="mango_bubble_teas", lb=0)

# Objective function: Maximize profit
model.setObjective(4*T + 6*M, GRB.MAXIMIZE)

# Constraints
model.addConstr(3*T <= 60, "taro_availability")
model.addConstr(3*M <= 60, "mango_availability")
model.addConstr(4*T + 5*M <= 140, "milk_availability")

# Optimize the model
model.optimize()

# Print results
if model.status == GRB.OPTIMAL:
    print("Optimal solution found:")
    print(f"Taro bubble teas: {T.x}")
    print(f"Mango bubble teas: {M.x}")
    print(f"Total profit: ${4*T.x + 6*M.x:.2f}")
else:
    print("No optimal solution found")

```